一文帶你瞭解vue2之響應式原理

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在面試的過程中也會問到:","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"請闡述vue2的響應式原理?","attrs":{}}],"attrs":{}},{"type":"text","text":",凡是出現闡述或者理解,一般都是知無不言言無不盡,知道多少說多少。接下來,我來談談自己的理解,切記不要去背,一定要理解之後,用自己的語言來描述出來。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那什麼是響應式呢?響應式就是當對象本身(對象的增刪值)或者對象屬性(重新賦值)發生變化時,將會運行一些函數,最常見的就是render函數。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在具體實現上,vue用到了幾個核心部件,每一個部件都解決一個問題:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"Observer","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"Dep","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"Watcher","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"Scheduler","attrs":{}}]}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"🌟 Observer","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Observer","attrs":{}}],"attrs":{}},{"type":"text","text":"要實現的目標非常簡單,就是把一個普通的對象轉換爲響應式的對象。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了實現這一點,Observer把對象的每個屬性通過","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Object.defineProperty","attrs":{}}],"attrs":{}},{"type":"text","text":"轉換爲帶有","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"getter","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"setter","attrs":{}}],"attrs":{}},{"type":"text","text":"的屬性,這樣一來,我們訪問或設置屬性時,會分別調用getter和setter方法,vue就有機會做一些別的事情。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/34/340a80b4cc15f090b25b495a31e1c3c9.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Observer是vue內部的構造器,我們可以通過Vue提供的靜態方法","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Vue.observable(object)","attrs":{}}],"attrs":{}},{"type":"text","text":"間接的使用該功能。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"示例","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"js"},"content":[{"type":"text","text":"\n \n \n \n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"運行結果","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"輸出結果中的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"...","attrs":{}}],"attrs":{}},{"type":"text","text":"代表數據是響應式的,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Invoke property getter","attrs":{}}],"attrs":{}},{"type":"text","text":"表示調用了屬性的getter方法。如果對象中還存在對象,那麼它會深度遞歸遍歷,讓所有的數據都是響應式的數據。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/50/502e60dba473b9ad716d5949a06ccb24.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果說在組件當中,配置中的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"data","attrs":{}}],"attrs":{}},{"type":"text","text":"也會返回一個響應式數據,這一過程在組件生命週期中發生在","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"beforeCreate","attrs":{}}],"attrs":{}},{"type":"text","text":"之後,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"created","attrs":{}}],"attrs":{}},{"type":"text","text":"之後","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Observer在具體實現上,它會遞歸遍歷對象的所有屬性,以完成數據響應式的轉換。如果說一個屬性一開始並不存在於對象中,是後面添加上的,那麼這種屬性是檢測不到的,所以像之前使用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"obj.e = 3","attrs":{}}],"attrs":{}},{"type":"text","text":"新增一個","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"e:3","attrs":{}}],"attrs":{}},{"type":"text","text":"也是檢測不到的,因爲之前對象中沒有。但是到了vue3,使用了proxy,那就可以檢測到了。因此在vue2中提供了","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"$set","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"$delete","attrs":{}}],"attrs":{}},{"type":"text","text":"兩個實例方法,我們可以通過這兩個實例方法對已有響應式對象添加或刪除屬性。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"示例","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過對暱稱的刪除和年齡的添加,對比","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"$set","attrs":{}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"$delete","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"delete","attrs":{}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"set","attrs":{}}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"js"},"content":[{"type":"text","text":"\n\n
\n

暱稱:{{obj.name}}

\n

年齡:{{obj.age}}

\n \n \n \n
\n\n \n \n \n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"運行結果","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/2a/2a66165217bca9663456a70970fedcfd.gif","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從運行結果來看,在沒有點擊","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"刪除暱稱","attrs":{}}],"attrs":{}},{"type":"text","text":"按鈕之前","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"vm.obj","attrs":{}}],"attrs":{}},{"type":"text","text":"輸出的name是響應式數據,點擊刪除暱稱按鈕之後再次打印vm.obj此時數據已經被刪除,但是頁面上暱稱法醫並未刪除,vue收不到屬性被刪除的通知,因爲","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"delete obj.name","attrs":{}}],"attrs":{}},{"type":"text","text":"是不會被檢測到的","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下來使用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"$delete","attrs":{}}],"attrs":{}},{"type":"text","text":"進行暱稱的刪除操作:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"js"},"content":[{"type":"text","text":"\n
\n

暱稱:{{obj.name}}

\n

年齡:{{obj.age}}

\n \n \n \n
\n\n \n \n \n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"運行結果","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d6/d6072d0e9afdf8391ff729ba907502dd.gif","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當使用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"$delete","attrs":{}}],"attrs":{}},{"type":"text","text":"的時候vue就會收到通知了,進行暱稱刪除操作,頁面也會及時響應。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同理,我們來看看","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"$set","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"set","attrs":{}}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"js"},"content":[{"type":"text","text":" \n
\n

暱稱:{{obj.name}}

\n

年齡:{{obj.age}}

\n \n \n \n \n
\n\n \n \n \n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"運行結果","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ac/ac6721bca7ea2a02fdf8005c2bb7534b.gif","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當使用傳統方式","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"obj.age=100","attrs":{}}],"attrs":{}},{"type":"text","text":"向對象添加屬性的時候,其實可以添加成功的,只是數據並不是響應式的,頁面上沒有顯示年齡。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下來就使用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"$set","attrs":{}}],"attrs":{}},{"type":"text","text":"添加屬性:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"js"},"content":[{"type":"text","text":"\n
\n

暱稱:{{obj.name}}

\n

年齡:{{obj.age}}

\n \n \n \n
\n\n \n \n \n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"運行結果","attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/0d/0db3c2ceaf5575ae0297cac93618c00e.gif","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一目瞭然,使用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"$set","attrs":{}}],"attrs":{}},{"type":"text","text":"添加的屬性是響應式的,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"age:(...)","attrs":{}}],"attrs":{}},{"type":"text","text":"三個點很明顯。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以上就是針對對象的檢測,那麼數組呢?數組又是怎樣檢測的呢?Object和Array的變化檢測處理方式是不同的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於數組,vue會更改它的隱式原型,之所以這樣做,是因爲vue需要監聽那些可能改變數組內容的 方法。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d3/d307575b9b095236da04d93fb1e4a88d.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"總之,Observer的目標,就是要讓一個對象,它的屬性的讀取、賦值,內部數組的變化都要能夠被vue檢測到,這樣才能讓數據轉換爲響應式數據。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"🌼 Dep","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在有兩個問題沒有解決,就是讀取屬性時要做什麼事情?屬性變化時要做什麼事情?這個問題就需要","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Dep","attrs":{}}],"attrs":{}},{"type":"text","text":"來解決。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Dep的全稱是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Dependency","attrs":{}}],"attrs":{}},{"type":"text","text":",表示","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"依賴","attrs":{}}],"attrs":{}},{"type":"text","text":"的意思,","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Vue會爲響應式對象中的每個屬性、對象本身、數組本身創建一個Dep實例,每個Dep實例都有能力做以下兩件事:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"記錄依賴:是誰在用我","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"派發更新:我變了,我要通知那些用到我的人","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當讀取響應式對象的某個屬性時,它會進行依賴收集:有人用到了我","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當改變某個屬性時,它會派發更新:那些用我的人聽好了,我變了","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/94/943dba61f97d2ecf720015a3f0739526.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"🔥 Watcher","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在又有一個問題,就是Dep如何知道是誰在用我?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"要解決這個問題,需要依靠另一個東西,就是Watcher","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當某個函數執行的過程中,用到了響應式數據,響應式數據是無法知道是哪個函數在用自己,因此,vue通過一種巧妙的辦法來解決這個問題:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們不要直接執行函數,而是把函數交給一個叫做watcher的東西去執行,watcher是一個對象,每個這樣的函數執行時都應該創建一個watcher,通過watcher去執行。watcher會創建一個全局變量,讓全局變量記錄當前負責執行的watcher等於自己,然後再去執行函數,在函數執行的過程中,如果發生了依賴記錄","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"dep.depend()","attrs":{}}],"attrs":{}},{"type":"text","text":",那麼Dep就會把這個全局變量記錄下來,表示:有一個watcher用到了我這個屬性","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當Dep進行派發更新時,它會通知之前記錄的所有watcher:我變了","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/fc/fc0942400a1e033a21d71dac1dd1a9e8.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"每一個vue組件實例,都至少對應一個watcher,該watcher中記錄了該組件的render函數。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"watcher首先會把render函數運行一次以收集依賴,於是那些在render中用到的響應式數據就會記錄這個watcher。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當數據變化時,dep就會通知該watcher,而watcher將重新運行render函數,從而讓界面重新渲染,同時重新記錄當前的依賴。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"🌴 Scheduler","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在還剩最後一個問題,就是Dep通知watcher之後,響應數據又多次改變,造成watcher執行重複運行對應函數,就有可能導致函數頻繁運行,從而導致效率低下","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"試想,如果一個交給watcher的函數,它裏面用到了屬性a、b、c、d,那麼a、b、c、d屬性都會記錄依賴,於是下面的代碼將會觸發4次更新:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"js"},"content":[{"type":"text","text":"state.a = \"new data\";\nstate.b = \"new data\";\nstate.c = \"new data\";\nstate.d = \"new data\";\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這樣肯定是不行的,因此,watcher收到派發更新的通知後,它不會立即執行對應render函數,當然不僅僅是render函數,還有可能是其它的函數,而是把自己交給一個叫","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"調度器","attrs":{}}],"attrs":{}},{"type":"text","text":"的東西,在調度器裏面有個隊列,可以認爲是一個數組,這個隊列數組中記錄了當前要運行哪些watcher,調度器維護一個執行隊列,在隊列中同一個watcher只會存在一次,隊列中的watcher不是立即執行,它會通過一個叫做","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"nextTick","attrs":{}}],"attrs":{}},{"type":"text","text":"的工具方法,把這些需要執行的watcher放入到","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"事件循環的微隊列","attrs":{}}],"attrs":{}},{"type":"text","text":"中,nextTick的具體做法是通過","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Promise","attrs":{}}],"attrs":{}},{"type":"text","text":"完成的,nextTick其實就是一個函數","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"js"},"content":[{"type":"text","text":"nextTick((fn)=>{\n Promise.resolve().then(fn);//通過這種方式就跑到微隊列中去了\n})\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"也就是說,當響應式數據變化時,render函數的執行是異步的,並且在微隊列中","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"👋 總體流程圖","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/99/99875ac809b316acfb9381bf71591748.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們簡單過一遍這個流程圖:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"原始對象通過","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Observer","attrs":{}}],"attrs":{}},{"type":"text","text":"將轉換成一個響應式的對象,具有","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"getter","attrs":{}}],"attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"setter","attrs":{}}],"attrs":{}},{"type":"text","text":"方法,然後就靜靜等待着。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"突然有一天,雷雨交加,有一個render函數要執行,但不是直接就執行了,而是交給watcher來執行,watcher通過設置全局變量的方式讀取數據,因爲讀取了數據,所以會觸發響應式對象的getter,隨後getter會從全局變量的位置讀取到當前正在讀取的watcher並把watcher收集到Dep中。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"通過以上步驟頁面就會被渲染出來了。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"又是突然的一天哈,風和日麗,我觸發了一個按鈕或者事件,不管幹了什麼,反正是數據改變了,進行新的步驟——","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"派發更新","attrs":{}}],"attrs":{}},{"type":"text","text":",隨後通知watcher,我變了哦,你給我馬上搞定這件事情,但是watcher並不是立即就執行的,因爲數據變動有時候不是一個,而是很多,立即執行的話會重複執行很多render函數或者其它數據變動的函數,執行效率會變低。然而watcher把自己交給調度器","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Scheduler","attrs":{}}],"attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"調度器會把watcher添加到隊列中,當然在隊列中也不會執行的,而是將隊列交給","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"nextTick","attrs":{}}],"attrs":{}},{"type":"text","text":"隊列,nextTick裏面的函數全是在微隊列的,等同步代碼執行完成後,會異步地執行函數fn1、fn2、watcher等等,這一步相當於重新執行了watcher,然後又重新執行了render函數,就這樣地循環往復。","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"😛 好了, 以上就是我今天的分享,大家對於vue2響應式原理還有什麼問題可以在評論區討論鴨~","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"記得點贊 👍 支持一下哦~ 😘","attrs":{}}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章