if (this.deep) {
traverse(value)
}
再接着根据 this.deep 来调用 traverse 。 traverse 的作用主要是递归遍历触发 value 的 getter ,调用所有元素的 dep.depend() 并过滤重复收集的 dep 。最后调用 popTarget() 将当前 watcher 移出栈,并执行 cleanupDeps :
cleanupDeps () {
let i = this.deps.length
while (i--) {
const dep = this.deps[i]
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this)
}
}
...
}
遍历 this.deps ,如果在 newDepIds 中不存在 dep.id ,则说明新的依赖里不包含当前 dep ,需要到 dep 的观察者列表里去移除当前这个 watcher ,之后便是 depIds 和 newDepIds 、 deps 和 newDeps 的值交换,并清空 newDepIds 和 newDeps 。到此完成了对 watcher 的求值操作,同时更新了新的依赖,最后返回 value 即可。
回到 createComputedGetter 接着看:
if (Dep.target) {
watcher.depend()
}
当执行计算属性的 getter 时,有可能表达式中还有别的计算属性依赖,此时我们需要执行 watcher.depend 将当前 watcher 的 deps 添加到 Dep.target 即可。最后返回求得的 watcher.value 即可。
总的来说我们从 this[key] 触发 watcher 的 get 函数,将当前 watcher 入栈,通过求值表达式将所需要的依赖 dep 收集到 newDepIds 和 newDeps ,并将 watcher 添加到对应 dep 的观察者列表,最后清除无效 dep 并返回求值结果,这样就完成了依赖关系的收集。
Watcher 的更新
以上我们了解了 watcher 的依赖收集和 dep 的观察者收集的基本原理,接下来我们了解下 dep 的数据更新时如何通知 watcher 进行 update 操作。
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
首先在 dep.notify 时,我们将 this.subs 拷贝出来,防止在 watcher 的 get 时候 subs 发生更新,之后调用 update 方法:
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
- 如果是 lazy ,则将其标记为 this.dirty = true ,使得在 this[key] 的 getter 触发时进行 watcher.evaluate 调用计算。
- 如果是 sync 同步操作,则执行 this.run ,调用 this.get 求值和执行回调函数 cb 。
- 否则执行 queueWatcher ,选择合适的位置,将 watcher 加入到队列去执行即可,因为和响应式数据无关,故不再展开。
小结
因为篇幅有限,只对数据绑定的基本原理做了基本的介绍,在这画了一张简单的流程图来帮助理解 Vue 的响应式数据,其中省略了一些 VNode 等不影响理解的逻辑及边界条件,尽可能简化地让流程更加直观:
