Vue源码窥探之依赖收集

我们之前了解到,Vue 的响应式对象过程,是对 props 和 data 依次执行 defineReactive 方法,那我们这里继续重温一下该方法的逻辑实现:
Vue源码窥探之依赖收集_第1张图片
defineReactive 方法内我们需要关注两点,一个是在开始先实例化一个 Dep ,另一个是 get 方法内部的一系列逻辑就是依赖收集的过程,最关键的是 dep.depend 。那说明整个依赖收集的过程都是和 Dep 息息相关的,所以我们需要对 Dep 一探究竟。
Vue源码窥探之依赖收集_第2张图片
Dep 是一个 Class,作用:是一个可观察对象,可以拥有多个订阅者。
其中有一个静态属性 target,因为同一时期只会有一个 watcher 被计算。还有一个 subs 实例属性,该属性保存了订阅者( 也就是Watcher )的数组。
Dep 实际上其实就是对 Watcher 的一种管理,两者相互搭配共同实现依赖收集的整个过程,所以我们有必要看一下 Watcher 的实现
Vue源码窥探之依赖收集_第3张图片
我们可以看到,Watcher 是一个 Class,在它的构造函数中定义和很多和 Dep 相关的属性:
Vue源码窥探之依赖收集_第4张图片
其中,this.depsthis.newDeps 表示 Watcher 实例持有的 Dep 实例的数组;而 this.depIdsthis.newDepIds 分别代表 this.depsthis.newDepsid Set。 另外 Watcher 还定义了一些原型方法,和依赖收集有关的有 getaddDepscleanupDeps 方法。

过程分析

依赖收集的触发时机就是触发了 Object.defineProperty 的 get 方法,那我们就从最开始说起,Vue 的 mount 过程是通过 mountComponent 函数,具体逻辑如下:
Vue源码窥探之依赖收集_第5张图片
当我们去实例化一个渲染 watcher 的时候,首先会进入 watcher 的构造函数逻辑,然后会执行 this.get() 方法,进入 get 函数,首先会执行 pushTarget 函数

// 这里使用一个栈类型去存储 render watcher ,主要是因为在嵌套组件中,组件的渲染也是嵌套进行的
export function pushTarget (_target: ?Watcher) {
  if (Dep.target) targetStack.push(Dep.target)
  Dep.target = _target
}

该函数主要负责把 Dep,target 赋值为当前的渲染 Watcher 并压入栈中。接着又执行了

value = this.getter.call(vm, vm)

this.getter 在初始化的时候已经被赋值为 updateComponent ,这实际就是在执行:

vm._update(vm._render(), hydrating)

而执行这段函数会首先执行 vm._render() 方法,这个方法会把 Tamplate 生成渲染 VNode,并且在这个过程中会对 vm 上的数据进行访问,这个时候就会触发数据对象的 getter。
那么每个对象值的 getter 中都会有一个 dep,那么在触发 getter 的时候就会调用 dep.depend() 方法,最终久会执行 Dep.target.addDep(this) ,而 Dep.target 又指向的是当前的渲染 watcher,那么相当于执行 watcheraddDep 方法:
Vue源码窥探之依赖收集_第6张图片
这其中会做一些逻辑判断,最终执行 dep.addSub(this),那么就相当于执行 this.subs.push(sub) ,也就是说把当前的 watcher 订阅到这个数据持有的 depsubs 中,这样就相当于订阅了该数据,那么当该数据更新的时候就会相应地得到通知。

所以,在 vm._render() 的过程中,会触发所有数据的 getter ,这样实际上已经完成了一个依赖收集的过程。最后当前 vm 的数据依赖收集完毕后,后相应的把渲染 Dep.target 也改变为之前的渲染 watcher ,即执行

popTarget()

也就相当于执行

Dep.target = targetStack.pop()

最后,我们可以看到, WatcherDep 相当绕。两者相互配合完成了观察者模式,进行了依赖收集和派发更新的过程。

你可能感兴趣的:(js,vue)