代码是程序员交流最好的语言,首先来一个例子
new Vue({
template:
`<div>
<span>{{text1}}</span>
<span>{{computedText1}}</span>
<span>{{computedText2}}</span>
<div>`,
data () {
return {
text1: 'text1',
}
},
computed: {
computedText1: function getComputed1 () {
return this.text1 + '2'
},
computedText2: function getComputed2 () {
return this.computedText1 + '3'
}
}
})
代码中,data.text1
的变化,会引起computedText1
的重新计算,而computedText1
的重新计算又会引起computedText2
的重新计算。那么这是如何实现的呢,下文将以较为浅显易懂的方式进行讲解。
发布-订阅
如上图,发布者会把消息分发给订阅者,然后订阅者根据消息内容作出反应。对应到vue上,则是:
如上图,每个data或者computed属性,都有对应的watcher对象和Dep对象。Dep对象中有一个队列,记录了它的订阅者(or观察者)。
所以,data.text1
的dep中有computedText1
的watcher,computedText1
的dep中有computedText2
的watcher。一旦data.text1
的值发生变化(被set),则
被nofify后,watcher会进行update,而一般情况下,update只是把dirty变量设置为true,然后安安静静等待下一个tick才进行计算和视图修改(都是为了性能,你懂得)。
当然,下一个tick后,怎么找到dirty为true计算属性的,请看下方源码
let watchers = vm._computedWatchers = Object.create(null)
for (const key in computed) {
const userDef = computed[key]
// getter值为computed中key的监听函数或对象的get值
let getter = typeof userDef === 'function' ? userDef : userDef.get
// 新建computed的 watcher
watchers[key] = new Watcher(vm, getter, noop, computedWatcherOptions)
if (!(key in vm)) {
// 定义计算属性
this.defineComputed(vm, key, userDef)
}
}
所以,_computedWatchers通过key-value的方式,索引了所有计算属性的watcher。要用的时候,通过key取,要找dirty为true的时候,遍历一下呗。
依赖收集
上面讲的,data属性的变化,如何传导到计算属性上的,通过set实现。
那么,接下来要讲的是,这个订阅关系是怎么收集的(即怎么样把对应的watcher放到对应的dep中),通过get实现。
简单的说,sub(订阅者)的get中调用了pub(发布者)的get。执行sub.get时,先把Dep.target指向sub,然后执行pub.get的时候,pub.dep.addSub(sub)。然后由于pub的get中可能还调用了其他data或者computed的get,所以继续重复上述步骤。
精简版
最后,要是觉得上文的内容还是太复杂,没关系,来个依赖收集一分钟入门精简版。以计算属性computedText1和data属性text1为例,如下
- computedText1.get的时候,进行依赖收集(computedText1订阅data.text1)
- data.text1.set的时候,通知computedText1,computedText1收到消息后,会在下一个tick进行重新计算