淺析Vue原始碼(八)——依賴收集與監聽

DIVI發表於2018-10-08

這一章主要講的是在render的時候如何做到對data中元素進行依賴的收集與監聽。

下面讓我們看看這行程式碼:

new Vue({ 
template: `<
div>
<
span>
span1:<
/span>
{{span1
}
} <
span>
span2:<
/span>
{{span2
}
} <
div>
`, data: {
span1: 'span1', span2: 'span2', span3: 'span3'
}
});
複製程式碼

按照之前我們理解的data中依賴收集與監聽方法進行繫結則會出現一個問題——span3在實際模板中並沒有被用到,然而當span3的資料被修改的時候(this.span3 = ‘span4’)的時候,同樣會觸發span3的setter導致重新執行渲染,這顯然不符合我們所想要的。

Dep

當對data上的物件進行修改值的時候會觸發它的setter,那麼取值的時候自然就會觸發getter事件,所以我們只要在最開始初始化的時候進行一次render,那麼所有被渲染所依賴的data中的資料就會被getter收集到Dep的subs中去。在對data中的資料進行修改的時候setter只會觸發Dep的subs的函式。

定義一個依賴收集類Dep。

class Dep { 
constructor () {
this.subs = [];

} /**進行依賴的收集**/ addSub (sub: Watcher) {
this.subs.push(sub)
} /**進行依賴的移除**/ removeSub (sub: Watcher) {
remove(this.subs, sub)
} /*Github:https://github.com/answershuto*/ /**進行依賴的遍歷通知**/ notify () {
// stabilize the subscriber list first const subs = this.subs.slice() for (let i = 0, l = subs.length;
i <
l;
i++) {
subs[i].update()
}
}
}function remove (arr, item) {
if (arr.length) {
const index = arr.indexOf(item) if (index >
-1) {
return arr.splice(index, 1)
}
}複製程式碼

Watcher

訂閱者,當依賴收集的時候會addSub到sub中,在修改data中資料的時候會觸發dep物件的notify,通知所有Watcher物件去修改對應檢視。

class Watcher { 
constructor (vm, expOrFn, cb, options) {
this.cb = cb;
this.vm = vm;
/*在這裡將觀察者本身賦值給全域性的target,只有被target標記過的才會進行依賴收集*/ Dep.target = this;
/*Github:https://github.com/answershuto*/ /*觸發渲染操作進行依賴收集*/ this.cb.call(this.vm);

} update () {
this.cb.call(this.vm);

}
}複製程式碼

開始依賴收集

class Vue { 
constructor(options) {
this._data = options.data;
observer(this._data, options.render);
let watcher = new Watcher(this, );

}
}function defineReactive (obj, key, val, cb) {
/*在閉包記憶體儲一個Dep物件*/ const dep = new Dep();
Object.defineProperty(obj, key, {
enumerable: true, configurable: true, get: ()=>
{
if (Dep.target) {
/*Watcher物件存在全域性的Dep.target中*/ dep.addSub(Dep.target);

}
}, set:newVal=>
{
/*只有之前addSub中的函式才會觸發*/ dep.notify();

}
})
}Dep.target = null;
複製程式碼

將觀察者Watcher例項賦值給全域性的Dep.target,然後觸發render操作只有被Dep.target標記過的才會進行依賴收集。有Dep.target的物件會將Watcher的例項push到subs中,在物件被修改出發setter操作的時候dep會呼叫subs中的Watcher例項的update方法進行渲染。

詳細過程可以參考我之前寫的淺析Vue原始碼(三)—— initMixin(下),在這裡面具體介紹了Dep,Watcher,defineReactive的方法。

感謝染陌老師提供的思路。要是喜歡的話可以給我個star, github

來源:https://juejin.im/post/5bba1f766fb9a05cec4dc380

相關文章