響應式資料的最終目标,是當對象本身或對象屬性發生變化時,将會運作一些函數,最常見的就是render函數。
在具體實作上,vue用到了幾個核心部件:
- Observer
- Dep
- Watcher
- Scheduler
Observer
Observer要實作的目标非常簡單,就是把一個普通的對象轉換為響應式的對象
為了實作這一點,Observer把對象的每個屬性通過
Object.defineProperty
轉換為帶有
getter
和
setter
的屬性,這樣一來,當通路或設定屬性時,
vue
就有機會做一些别的事情。這些 getter/setter 對使用者來說是不可見的,但是在内部它們讓 Vue 能夠追蹤依賴,在 property 被通路和修改時通知變更。
Observer是vue内部的構造器,我們可以通過Vue提供的靜态方法
Vue.observable( iterator )
間接的使用該功能。
在元件生命周期中,這件事發生在
beforeCreate
之後,
created
之前。
具體實作上,它會遞歸周遊對象的所有屬性,以完成深度的屬性轉換。
由于 JavaScript 的限制,Vue 不能檢測數組和對象的變化(無法檢測property的添加或移除)。盡管如此我們還是有一些辦法來回避這些限制并保證它們的響應性。是以
vue
提供了
$set
和
$delete
兩個執行個體方法,讓開發者通過這兩個執行個體方法對已有響應式對象添加或删除屬性。
let vm = new Vue({
data:{
a:0
}
})
// vm.a 是響應式的
vm.q = 2
// vm.q 是非響應式的
Vue不能檢測數組的以下變動:
- 當你利用索引直接設定一個數組項時,例如:
vm.items[index] = newValue
- 當你修改數組的長度時,例如:
vm.items.length = newLength
對于數組,
vue
會更改它的隐式原型,之是以這樣做,是因為vue需要監聽那些可能改變數組内容的方法,改動數組可以通過
vm.$set
執行個體方法,該方法是全局方法
Vue.set
的一個别名

總之,Observer的目标,就是要讓一個對象,它屬性的讀取、指派,内部數組的變化都要能夠被vue感覺到。
Dep
這裡有兩個問題沒解決,就是讀取屬性時要做什麼事,而屬性變化時要做什麼事,這個問題需要依靠Dep來解決。
Dep的含義是
Dependency
,表示依賴的意思。
Vue
會為響應式對象中的每個屬性、對象本身、數組本身建立一個
Dep
執行個體,每個
Dep
執行個體都有能力做以下兩件事:
- 記錄依賴:是誰在用我
- 派發更新:我變了,我要通知那些用到我的人
當讀取響應式對象的某個屬性時,它會進行依賴收集:有人用到了我
當改變某個屬性時,它會派發更新:那些用我的人聽好了,我變了
這裡又出現一個問題,就是Dep如何知道是誰在用我?
要解決這個問題,需要依靠另一個東西,就是Watcher。
當某個函數執行的過程中,用到了響應式資料,響應式資料是無法知道是哪個函數在用自己的
是以,vue通過一種巧妙的辦法來解決這個問題
我們不要直接執行函數,而是把函數交給一個叫做watcher的東西去執行,watcher是一個對象,每個這樣的函數執行時都應該建立一個watcher,通過watcher去執行
watcher會設定一個全局變量,讓全局變量記錄目前負責執行的watcher等于自己,然後再去執行函數,在函數的執行過程中,如果發生了依賴記錄
dep.depend()
,那麼
Dep
就會把這個全局變量記錄下來,表示:有一個watcher用到了我這個屬性
當Dep進行派發更新時,它會通知之前記錄的所有watcher:我變了
每一個
vue
元件執行個體,都至少對應一個
watcher
,該
watcher
中記錄了該元件的
render
函數。
watcher
首先會把
render
函數運作一次以收集依賴,于是那些在render中用到的響應式資料就會記錄這個watcher。
當資料變化時,dep就會通知該watcher,而watcher将重新運作render函數,進而讓界面重新渲染同時重新記錄目前的依賴。
Scheduler
現在還剩下最後一個問題,就是Dep通知watcher之後,如果watcher執行重運作對應的函數,就有可能導緻函數頻繁運作,進而導緻效率低下
試想,如果一個交給watcher的函數,它裡面用到了屬性a、b、c、d,那麼a、b、c、d屬性都會記錄依賴,于是下面的代碼将觸發4次更新:
state.a = "new data";
state.b = "new data";
state.c = "new data";
state.d = "new data";
這樣顯然是不合适的,是以,watcher收到派發更新的通知後,實際上不是立即執行對應函數,而是把自己交給一個叫排程器的東西
排程器維護一個執行隊列,該隊列同一個watcher僅會存在一次,隊列中的watcher不是立即執行,它會通過一個叫做
nextTick
的工具方法,把這些需要執行的watcher放入到事件循環的微隊列中,nextTick的具體做法是通過
Promise
完成的
nextTick 通過
this.$nextTick
暴露給開發者
nextTick 的具體處理方式見:https://cn.vuejs.org/v2/guide/reactivity.html#%E5%BC%82%E6%AD%A5%E6%9B%B4%E6%96%B0%E9%98%9F%E5%88%97
也就是說,當響應式資料變化時,
render
函數的執行是異步的,并且在微隊列中
總結
- 首先observer将一個原始的js對象轉換為響應式對象,把對象的每個屬性通過
轉換為帶有Object.defineProperty
和getter
的屬性,這樣一來,當通路或設定屬性時,setter
就有機會做一些别的事情。vue還提供了vue
和$set
兩個執行個體方法delete
-
會為響應式對象中的每個屬性、對象本身、數組本身建立一個Vue
執行個體,每個Dep
執行個體都有能力做以下兩件事:Dep
- 記錄依賴:是誰在用我
- 派發更新:我變了,我要通知那些用到我的人
- 當首次執行render函數時,vue會将render函數交給watcher,執行watcher會設定一個全局變量,讓全局變量記錄目前負責執行的watcher等于自己,然後再去執行函數,在函數的執行過程中,如果發生了依賴記錄
,那麼dep.depend()
Dep
就會把這個全局變量記錄下來,表示:有一個watcher用到了我這個屬性。當Dep進行派發更新時,它會通知之前記錄的所有watcher:我變了
自己,然後再去執行函數,在函數的執行過程中,如果發生了依賴記錄
,那麼dep.depend()
就會把這個全局變量記錄下來,表示:有一個watcher用到了我這個屬性。當Dep進行派發更新時,它會通知之前記錄的所有watcher:我變了Dep
- watcher收到派發更新的通知後,不是立即執行對應函數,而是把自己交給一個叫排程器的東西,排程器維護一個執行隊列,該隊列同一個watcher僅會存在一次,隊列中的watcher不是立即執行,它會通過一個叫做
的工具方法,把這些需要執行的watcher放入到事件循環的微隊列中,nextTick的具體做法是通過nextTick
完成的Promise