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 原型链,使得对于新插入的数据: