Watcher 的求值
因为计算属性是惰性求值,所以我们继续看 initComputed 循环体:
if (!(key in vm)) { defineComputed(vm, key, userDef) }
defineComputed 主要将 userDef 转化为 getter/setter 访问器,并通过 Object.defineProperty 将 key 设置到 vm 上,使得我们能通过 this[key] 直接访问到计算属性。接下来我们主要看下 userDef 转为 getter 中的 createComputedGetter 函数:
function createComputedGetter (key) { return function computedGetter () { const watcher = this._computedWatchers && this._computedWatchers[key] if (watcher) { if (watcher.dirty) { watcher.evaluate() } if (Dep.target) { watcher.depend() } return watcher.value } } }
利用闭包保存计算属性的 key ,在 getter 触发时,首先通过 this._computedWatchers[key] 获取到之前保存的 watcher ,如果 watcher.dirty 为 true 时调用 watcher.evaluate (执行 this.get() 求值操作,并将当前 watcher 的 dirty 标记为 false ),我们主要看下 get 操作:
get () { pushTarget(this) let value const vm = this.vm try { value = this.getter.call(vm, vm) } catch (e) { ... } finally { // "touch" every property so they are all tracked as // dependencies for deep watching if (this.deep) { traverse(value) } popTarget() this.cleanupDeps() } return value }
可以看到,求值时先执行 pushTarget(this) ,通过查阅 src/core/observer/dep.js ,我们可以看到:
Dep.target = null const targetStack = [] export function pushTarget (target: ?Watcher) { targetStack.push(target) Dep.target = target } export function popTarget () { targetStack.pop() Dep.target = targetStack[targetStack.length - 1] }
pushTarget 主要是把 watcher 实例进栈,并赋值给 Dep.target ,而 popTarget 则相反,把 watcher 实例出栈,并将栈顶赋值给 Dep.target 。 Dep.target 这个我们之前在 getter 里见到过,其实就是当前正在求值的观察者。这里在求值前将 Dep.target 设置为 watcher ,使得在求值过程中获取数据时触发 getter 访问器,从而调用 dep.depend ,继而执行 watcher 的 addDep 操作:
addDep (dep: Dep) { const id = dep.id if (!this.newDepIds.has(id)) { this.newDepIds.add(id) this.newDeps.push(dep) if (!this.depIds.has(id)) { dep.addSub(this) } } }
先判断 newDepIds 是否包含 dep.id ,没有则说明尚未添加过这个 dep ,此时将 dep 和 dep.id 分别加到 newDepIds 和 newDeps 。如果 depIds 不包含 dep.id ,则说明之前未添加过此 dep ,因为是双向添加的(将 dep 添加到 watcher 的同时也需要将 watcher 收集到 dep ),所以需要调用 dep.addSub ,将当前 watcher 添加到新的 dep 的观察者队列。