最近閑來在比較深入的學習vue的源碼,受益匪淺沪悲,在這邊記錄一些心得穷当,順便給自己定個小目標--自己實現(xiàn)一個簡單的vue框架提茁,不考慮錯誤檢查,不考慮邊界情況馁菜,包含vue最主要最基礎(chǔ)的流程茴扁。
第一篇文章,就來簡單說說vue的依賴收集汪疮。
首先峭火,何為依賴收集毁习?
用例1:我們使用vue的時候,經(jīng)常會用到$watch方法去監(jiān)聽屬性的改變卖丸,比如:
app.$watch('a', function () {
console.log('a is change')
})
用例2:再比如vue框架幫我們做的數(shù)據(jù)與視圖的綁定蜓洪,每次改變數(shù)據(jù)的時候,頁面也會跟著刷新坯苹,這些都和vue的依賴收集機制有著密切的關(guān)系隆檀。
從$watch方法說起,現(xiàn)在有一個obj={a:1}粹湃,要怎樣監(jiān)測到obj.a的改變恐仑,就需要使用Object.defineProperty為a這個屬性設(shè)置get方法,在get方法里面就能知道a的改變了为鳄。
首先先把vm.data里的數(shù)據(jù)代理到vm下:
function initData (vm) {
var data = vm.$options.data
// 通常每個data我們都是通過一個工廠方法返回一個新對象裳仆,避免重復(fù)實例化導(dǎo)致幾個實例用了同一個data,因此這里要執(zhí)行這個方法獲得data對象
data = vm._data = typeof data === 'function' ? data.call(vm) : data || {}
var keys = Object.keys(data)
var i = keys.length
while(i--) {
proxy(vm, keys[i])
}
observer(data)
}
function proxy (vm, key) {
Object.defineProperty(vm, key, {
configurable: true,
enumerable: true,
get: function () {
return vm._data[key]
},
set: function (val) {
vm._data[key] = val
}
})
}
然后需要一個Observer類孤钦,Observer可直譯為觀察者(這里的觀察者和觀察者模式并不一樣歧斟,不可混淆),Observer類做的事情就是使每個屬性可被觀測偏形,并添加上ob這個屬性:
/* Observe */
var Observer = (function () {
var Observer = function (data) {
Object.defineProperty(data, '__ob__', {
enumerable: false,
value: this
})
this.walk(data)
}
Observer.prototype.walk = function (data) {
var keys = Object.keys(data)
var i = keys.length
while (i--) {
defineReactive(data, keys[i], data[keys[i]])
}
}
return Observer
})()
function defineReactive (data, key, val) {
observer(val)
var dep = new Dep()
Object.defineProperty(data, key, {
get: function () {
if (Dep.target) {
dep.addSub(Dep.target)
}
return val
},
set: function (newVal) {
if (val === newVal) {
return
}
val = newVal
dep.notify()
}
})
}
function observer (data) {
if (typeof data !== 'object') {
return
}
new Observer(data)
}
在initData方法中調(diào)用observer方法静袖。上面的代碼會為每一個被Observer的屬性在閉包中定義一個dep實例,這個dep在getter被調(diào)用的時候如果存在Dep.target俊扭,就會去收集當前的Dep.target到閉包中dep實例的subs數(shù)組中
上面的代碼中可以看到队橙,還有一個Dep類,Dep負責收集與該屬性有依賴關(guān)系的Watcher萨惑,代碼很簡單:
/*Dep*/
var Dep = (function () {
var Dep = function () {
this.subs = []
}
// Dep.target是一個Watcher
Dep.target = null
Dep.prototype.addSub = function (sub) { // sub為Watcher
this.subs.push(sub)
}
Dep.prototype.notify = function () {
for (var i = 0, l=this.subs.length; i < l; i++) {
this.subs[i].update()
}
}
return Dep
})()
Dep是一個典型的發(fā)布訂閱模式捐康,有個subs數(shù)組,是一個Watcher數(shù)組庸蔼,用來收集所有這個屬性上的訂閱者解总,當這個屬性發(fā)生變化時候,那么在屬性的setter里就可以獲取到這個變化姐仅,然后調(diào)用閉包中維護的dep實例的dep.notify方法調(diào)用所有的訂閱者花枫。
那么剩下的就是Watcher這個訂閱者了,很多地方可能都會稱為觀察者萍嬉,但是在這里為了避免和Observer類混淆乌昔,我就稱為訂閱者:
/*Watcher*/
var Watcher = (function () {
var Watcher = function (vm, expOrFn, cb, options) {
if (typeof expOrFn === 'string') {
this.getter = function () {
return vm[expOrFn]
}
} else if (typeof expOrFn === 'function') {
// todo
}
this.vm = vm
this.cb = cb
this.value = this.get()
}
Watcher.prototype.get = function () {
Dep.target = this
let value = this.getter.call(this.vm)
Dep.target = null
return value
}
Watcher.prototype.update = function () {
// todo
this.cb()
}
return Watcher
})()
Watcher的構(gòu)造函數(shù)接收這幾個參數(shù)隙疚,vm(vue實例)壤追,expOrFn(要被訂閱的屬性),cb(回調(diào)方法)供屉,還有options(配置)行冰,
在這里expOrFn是被觀察的對象上的一個屬性名稱溺蕉,是一個字符串;但是在我們之前說的依賴收集用例2中悼做,數(shù)據(jù)與視圖的綁定時候的依賴收集疯特,這里的expOrFn就會直接是一個updateComponent方法,所以在這里統(tǒng)一把屬性也轉(zhuǎn)成一個方法this.getter肛走。
然后調(diào)用Watcher的get方法漓雅,this.value = this.get(),
在get方法中先把Dep的靜態(tài)屬性target設(shè)置為當前的實例,然后執(zhí)行this.getter方法朽色,此時邻吞,屬性的getter方法中就會執(zhí)行:
if (Dep.target) {
dep.addSub(Dep.target)
}
這樣就把這個Watcher實例收集到了閉包的dep中了
此時執(zhí)行如下代碼,就會按我們預(yù)想的葫男,輸出a is change和a is change2了
var app = new Vue({
data: function () {
return {
a: 1,
b: {
c: 2
}
}
}
})
app.$watch('a', function () {
console.log('a is change')
})
app.$watch('a', function () {
console.log('a is change2')
})
app.a = 3
總結(jié)一下基本的原理:
1.首先observer一個對象抱冷,使其中的參數(shù)都有g(shù)et,set方法梢褐;
2.watch一個屬性的時候旺遮,new 一個Watcher實例,并把Dep.target設(shè)置為當前的Watcher實例盈咳,然后獲取一次該屬性的值耿眉,觸發(fā)get方法中的依賴收集。
另外附上本篇完整的例子 附件
上面就是一個簡單的依賴收集的原理鱼响,如果去看vue的源碼跷敬,就會發(fā)現(xiàn)復(fù)雜很多,有很多代碼很多屬性我這里并沒有展示出來热押,這也是我看源碼時候的一些疑問:
比如:
1.數(shù)組的改變要怎樣被依賴收集
2.Computed屬性的Watch要怎樣實現(xiàn)
3.Observer的dep西傀,和Watcher上的deps分別是做什么用的
4.Watcher中的user,lazy桶癣,deep拥褂,sync分別是什么用的
今天先寫到這里,上面幾個問題且看下面的幾篇博文