Vue数据绑定简析小结(9)

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 的观察者队列。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/385.html