Vue2.x是怎麼收集依賴的
概述
說到 vue 的響應式原理,我們都能很快答出資料劫持和釋出者訂閱者模式,通過 Object.defineProperty 來劫持 getter 和 setter,在 getter 的時候訂閱依賴,在 setter 的時候釋出響應執行依賴,從而達到響應式的目的。
但是如果深入一點,它是怎麼收集、釋出、管理依賴的呢?或者說,原始碼裡面的 defineReactive、Dep、Watcher 之間有什麼樣的關係呢?
我通過自己寫了一個簡易的響應式系統弄懂了這些,記錄下來,供以後工作時參考,相信對其他人也有用。
小型的響應系統
為了回答上面的問題,我打算寫一個簡易的的響應式系統,為了簡便,這個系統只考慮沒有巢狀的物件,程式碼如下:
function defineReactive(obj, key, val) { const dep = new Dep(); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get() { if (Dep.target) { dep.depend(); } return val; }, set(newVal) { val = newVal; dep.notify(); } }); } class Dep { constructor() { this.subs = []; } addSub(sub) { this.subs.push(sub); } removeSub() { const index = this.subs.indexOf(sub); if (index > -1) { this.subs.splice(index, 1); } } depend() { if (Dep.target) { Dep.target.addDep(this); } } notify() { const subs = this.subs.slice(); for (let i = 0, l = subs.length; i < l; i++) { subs[i].update(); } } } Dep.target = null; class Watcher { constructor(cb) { this.getter = cb; this.deps = []; this.value = this.get(); } get() { Dep.target = this; const value = this.getter(); console.log('Dep.target', Dep.target); return value; } addDep(dep) { dep.addSub(this); } update() { const value = this.get(); } } const obj = {}; defineReactive(obj, 'text', 'Hello World!'); const watcher = new Watcher(() => { document.querySelector('body').innerHTML = obj.text; });
把上面的程式碼複製到瀏覽器的控制檯執行,就可以看到瀏覽器裡面出現了Hello World
,然後我們繼續在控制檯輸入obj.text = 'Define Reactive'
,可以看到瀏覽器裡面的Hello World
就變成了Define Reactive
。
結合 Vue 的生命週期
我們把上面的例子帶入 Vue 的生命週期裡面看:
1.首先我們知道,在beforeCreate
階段,Vue會進行各種初始化,比如事件、生命週期等,其實就等效於這段程式碼:
const obj = {};
2.在created
階段,Vue會初始化狀態:data、compute、props、methods等,那其實就等效於這段程式碼:
defineReactive(obj, 'text', 'Hello World!');
3.在beforeMount
階段,Vue會對 vm 建立一個 watcher,當變動的時候,使用 update 進行更新,這就等效於這段程式碼:
const watcher = new Watcher(() => {
document.querySelector('body').innerHTML = obj.text;
});
總結
1.必須先用defineReactive
設定為響應式的,然後才能在watcher
裡面實現依賴收集,並且例項化多個watcher
都能被收集到。
2.總的來說,每一個 dep 例項其實就是一個訂閱中心,它通過閉包存在,然後在通過 defineReactive 把資料轉變為響應式之後,此時的資料沒有任何變化,但是一旦例項化了一個 watcher,資料就會自動把這個 watcher 新增到自己的訂閱中心,當改變這個資料的時候,也會自動釋出通知,達到更新訂閱的 watcher 的目的。
3.vue2.x的原始碼的響應式系統除了上面的程式碼,還做了很多別的工作,比如觀測巢狀物件、觀測陣列、處理已經存在的getter和setter、對 watcher 進行排程等等。