轻轻松松读懂vue依赖收集(computed)

1,968 阅读2分钟

代码是程序员交流最好的语言,首先来一个例子

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上,则是:

发布-订阅者模式-vue

如上图,每个data或者computed属性,都有对应的watcher对象和Dep对象。Dep对象中有一个队列,记录了它的订阅者(or观察者)。

所以,data.text1的dep中有computedText1的watcher,computedText1的dep中有computedText2的watcher。一旦data.text1的值发生变化(被set),则

发布-订阅者模式-vue

被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进行重新计算

发布阶段