Vue数据绑定简析小结(6)
首先在 getter 调用时,判断 Dep.target 是否存在,若存在则调用 dep.depend 。我们先不深究 Dep.target ,只当它是一个观察者,比如我们常用的某个计算属性,调用 dep.depend 会将 dep 当做计算属性的依赖项存入其依赖列表,并把这个计算属性注册到这个 dep 。这里为什么需要互相引用呢?这是因为一个 target[key] 可以充当多个观察者的依赖项,同时一个观察者可以有多个依赖项,他们之间属于多对多的关系。这样当某个依赖项改变时,我们可以根据 dep 里维护的观察者,调用他们的注册方法。现在我们回过头来看 Dep :
// src/core/observer/dep.js export default class Dep { static target: ?Watcher; id: number; subs: Array<Watcher>; constructor () { this.id = uid++ this.subs = [] } addSub (sub: Watcher) { this.subs.push(sub) } removeSub (sub: Watcher) { remove(this.subs, sub) } depend () { if (Dep.target) { Dep.target.addDep(this) } } notify () { // stabilize the subscriber list first const subs = this.subs.slice() ... for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } }
构造函数里,首先添加一个自增的 uid 用以做 dep 实例的唯一性标志,接着初始化一个观察者列表 subs ,并定义了添加观察者方法 addSub 和移除观察者方法 removeSub 。可以看到其在 getter 中调用的 depend 会将当前这个 dep 实例添加到观察者的依赖项,在 setter 里调用的 notify 会执行各个观察者注册的 update 方法, Dep.target.addDep 这个方法将在之后的 Watcher 里进行解释。简单来说就是会在 key 的 getter 触发时进行 dep 依赖收集到 watcher 并将 Dep.target 添加到当前 dep 的观察者列表,这样在 key 的 setter 触发时,能够通过观察者列表,执行观察者的 update 方法。
当然,在 getter 中还有如下几行代码:
if (childOb) { childOb.dep.depend() if (Array.isArray(value)) { dependArray(value) } }
这里可能会有疑惑,既然已经调用了 dep.depend ,为什么还要调用 childOb.dep.depend ?两个 dep 之间又有什么关系呢?
其实这两个 dep 的分工是不同的。对于数据的增、删,利用 childOb.dep.notify 来调用观察者方法,而对于数据的修改,则使用的 dep.notify ,这是因为 setter 访问器无法监听到对象数据的添加和删除。举个例子:
const data = { arr: [{ value: 1 }], } data.a = 1; // 无法触发setter data.arr[1] = {value: 2}; // 无法触发setter data.arr.push({value: 3}); // 无法触发setter data.arr = [{value: 4}]; // 可以触发setter
还记得 Observer 构造函数里针对数组类型 value 的响应式转换吗?通过重写 value 原型链,使得对于新插入的数据: