我们之前了解到,Vue 的响应式对象过程,是对 props 和 data 依次执行 defineReactive
方法,那我们这里继续重温一下该方法的逻辑实现:
defineReactive
方法内我们需要关注两点,一个是在开始先实例化一个 Dep
,另一个是 get 方法内部的一系列逻辑就是依赖收集的过程,最关键的是 dep.depend
。那说明整个依赖收集的过程都是和 Dep
息息相关的,所以我们需要对 Dep
一探究竟。
Dep
是一个 Class,作用:是一个可观察对象,可以拥有多个订阅者。
其中有一个静态属性 target,因为同一时期只会有一个 watcher 被计算。还有一个 subs 实例属性,该属性保存了订阅者( 也就是Watcher
)的数组。
Dep
实际上其实就是对 Watcher
的一种管理,两者相互搭配共同实现依赖收集的整个过程,所以我们有必要看一下 Watcher
的实现
我们可以看到,Watcher
是一个 Class,在它的构造函数中定义和很多和 Dep
相关的属性:
其中,this.deps
和 this.newDeps
表示 Watcher
实例持有的 Dep
实例的数组;而 this.depIds
和 this.newDepIds
分别代表 this.deps
和 this.newDeps
的 id
Set。 另外 Watcher 还定义了一些原型方法,和依赖收集有关的有 get
、addDeps
和 cleanupDeps
方法。
过程分析
依赖收集的触发时机就是触发了 Object.defineProperty
的 get 方法,那我们就从最开始说起,Vue 的 mount 过程是通过 mountComponent
函数,具体逻辑如下:
当我们去实例化一个渲染 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
,那么相当于执行 watcher
的 addDep
方法:
这其中会做一些逻辑判断,最终执行 dep.addSub(this)
,那么就相当于执行 this.subs.push(sub)
,也就是说把当前的 watcher
订阅到这个数据持有的 dep
的 subs
中,这样就相当于订阅了该数据,那么当该数据更新的时候就会相应地得到通知。
所以,在 vm._render()
的过程中,会触发所有数据的 getter ,这样实际上已经完成了一个依赖收集的过程。最后当前 vm
的数据依赖收集完毕后,后相应的把渲染 Dep.target
也改变为之前的渲染 watcher
,即执行
popTarget()
也就相当于执行
Dep.target = targetStack.pop()
最后,我们可以看到, Watcher
和 Dep
相当绕。两者相互配合完成了观察者模式,进行了依赖收集和派发更新的过程。