前言
首先要明白的是,vue内发布订阅模式的机制是:Watcher对象去订阅Dep对象,每一个Dep对象都绑定了一个vue.data/computed/props属性,也就是说Watcher最终订阅到了某个具体属性。
当属性值发生改变时,Dep对象会去notify所有订阅他的Watcher对象,Watcher收到信号就去update
本文章属于vue源码的简化版,只是为了方便理解,比源码少了些东西
1
初始化时vue对象给所有computed属性加个Watcher实例,以便于订阅具体的vm.$data属性值
并将所有computed属性通过defineProperties绑定到vm上,以便可以通过vm[key]直接获取到值
vm._computedWatchers = []
for(const key in computed) {
// 给所有computed属性加个Watcher实例
const getter = typeof computed[key] === 'function' ? computed[key] : computed[key].get
vm._computedWatchers.push(new Watcher(vm, getter || noop, noop))
// 将computed属性绑定到vm上
Object.defineProperties(vm, key, {
get: function() {
var watcher = this._computedWatchers[key];
if (watcher) {
// dirty表示脏值
// 如果此watcher已经'脏'了,就去重新获取值,否则直接返回watcher缓存的value值即可
if (watcher.dirty) {
watcher.get();
}
return watcher.value
}
}
})
}
2
假如computed是这么写的:
computed: {
b() {
return this.a + 1
}
}
当vue对象中首次获取b时,会触发第1步定义的get,因为dirty初始为true,所以会执行watcher.get()去计算值
function Watcher(vm, getter){
this.getter = getter
this.dirty = true
this.value = undefined
}
Watcher.prototype.get = function(){
Dep.target = this
this.value = this.getter()
this.dirty = false
}
请注意,watcher.get()把当前Watcher缓存到Dep.target了
当调用到this.getter()时会去获取this.a,那么会触发a的getter:
function defineReactive(obj, key, val, customSetter, shallow) {
// 这里源码上并没有传参,这里加了个参数是为了更好的说明dep和属性的绑定关系,实际上是通过闭包绑定的
const dep = new Dep(vm, key)
Object.defineProperty(obj, key, {
get: function reactiveGetter() {
var value = val
if (Dep.target) {
// 让Dep.target订阅dep
dep.depend()
return value
}
},
}
在getter内部,如果有Dep.target,会让这个对象去订阅本属性,从而每次this.a发生变化后,会去调用Watcher的update
Watcher.prototype.update = function(){
this.dirty = true
}
update内部更新此Watcher是dirty的,已经脏了,那么下次再获取b的时候,就会如第1步所写,会再次调用Watcher的getter去重新取值,而如果this.a不发生改变,如第1步所写,会直接返回Watcher缓存的value