Vue.js 源码分析:依赖收集过程中的 targetStack 设计
Vue.js 源码分析:依赖收集过程中的 targetStack 设计
【免费下载链接】vue-analysis :thumbsup: Vue.js 源码分析 项目地址: https://gitcode.com/gh_mirrors/vu/vue-analysis
在 Vue.js 的响应式系统中,依赖收集是实现数据驱动视图的核心机制。当组件渲染或计算属性求值时,Vue 需要精确追踪哪些数据被使用,以便在数据变化时能触发相应的更新。targetStack 设计正是为解决嵌套依赖收集问题而诞生的关键实现,它确保了在复杂场景下依赖关系的准确性。
依赖收集的基本流程
Vue 的依赖收集基于发布-订阅模式,主要涉及三个核心角色:
- 数据劫持(Observer):通过
Object.defineProperty拦截数据访问,位于 vue/src/core/observer/index.js - 依赖管理器(Dep):管理数据与 Watcher 的订阅关系,位于 vue/src/core/observer/dep.js
- 观察者(Watcher):接收数据变化通知并触发更新,位于 vue/src/core/observer/watcher.js
当组件渲染时,会创建一个渲染 Watcher 并执行其 get 方法。此时需要将当前 Watcher 设为全局目标(Dep.target),使数据访问时能将该 Watcher 加入依赖列表。
targetStack 解决的核心问题
在单一线程执行环境中,如果出现嵌套的 Watcher 执行(如计算属性 Watcher 内部访问另一个计算属性),直接覆盖 Dep.target 会导致外层 Watcher 的依赖关系丢失。targetStack 通过栈结构保存 Watcher 调用链,确保每个 Watcher 执行完毕后能正确恢复上一层上下文。
// [vue/src/core/observer/dep.js](https://link.gitcode.com/i/e756d78455ef6a7e05e7db12adf21916)
Dep.target = null
const targetStack = []
export function pushTarget (_target: ?Watcher) {
if (Dep.target) targetStack.push(Dep.target)
Dep.target = _target
}
export function popTarget () {
Dep.target = targetStack.pop()
}
工作原理与代码实现
1. 入栈操作(pushTarget)
当 Watcher 开始执行 get 方法时,通过 pushTarget(this) 将自身压入栈中:
// [vue/src/core/observer/watcher.js](https://link.gitcode.com/i/85e53b95f39d1d1f5e06289c321158e4)
get () {
pushTarget(this) // 将当前 Watcher 入栈
let value
try {
value = this.getter.call(vm, vm) // 执行 getter 触发数据访问
} finally {
if (this.deep) {
traverse(value) // 深度观测
}
popTarget() // 恢复上一层 Watcher
this.cleanupDeps()
}
return value
}
2. 出栈操作(popTarget)
当 Watcher 执行完毕后,通过 popTarget() 恢复栈顶的 Watcher 为当前目标。这种设计确保了:
- 内层 Watcher 执行时不会覆盖外层上下文
- 多个嵌套 Watcher 能正确收集各自的依赖
3. 依赖收集流程
典型应用场景
1. 计算属性嵌套
当计算属性 A 依赖计算属性 B 时,targetStack 会依次保存 A 和 B 的 Watcher:
computed: {
a() { return this.b + 1 },
b() { return this.c * 2 }
}
执行顺序:
- 渲染 Watcher 入栈(Dep.target = 渲染 Watcher)
- 访问
a→ 计算属性 A 的 Watcher 入栈(Dep.target = A) - 访问
b→ 计算属性 B 的 Watcher 入栈(Dep.target = B) - 访问
c→ B 收集c的依赖 - B 出栈 → Dep.target 恢复为 A
- A 收集
b的依赖 - A 出栈 → Dep.target 恢复为渲染 Watcher
- 渲染 Watcher 收集
a的依赖
2. Watcher 嵌套执行
用户自定义 Watcher 内部访问计算属性时,同样通过栈结构保证依赖正确:
watch: {
x() {
console.log(this.computedY) // 触发 computedY 的 Watcher
}
},
computed: {
computedY() { return this.y + 1 }
}
与相关模块的协作
1. 与 Dep 的协作
Dep 通过 depend 方法将当前 Dep.target 添加到依赖列表:
// [vue/src/core/observer/dep.js](https://link.gitcode.com/i/e756d78455ef6a7e05e7db12adf21916)
depend () {
if (Dep.target) {
Dep.target.addDep(this) // 当前 Watcher 添加对 Dep 的订阅
}
}
2. 与 Scheduler 的协作
当数据变化时,Dep 通知所有订阅的 Watcher,通过 scheduler.js 进行批量更新,避免重复渲染。
设计亮点与最佳实践
- 栈结构的天然适配:JavaScript 执行栈与
targetStack的嵌套模型高度契合,保证了上下文切换的准确性 - 最小侵入性:通过
push/pop封装上下文管理,不影响 Watcher 和 Dep 的核心逻辑 - 性能优化:避免了重复的依赖收集,确保每轮渲染只收集当前上下文所需的依赖
总结
targetStack 作为 Vue 响应式系统的上下文管理中枢,通过简单而高效的栈结构,解决了复杂场景下的依赖收集冲突问题。这种设计不仅保证了数据驱动的准确性,也为 Vue 的计算属性、嵌套 Watcher 等高级特性提供了底层支撑。理解其实现原理,有助于开发者深入掌握 Vue 的响应式机制,编写更高效的响应式代码。
相关模块完整路径:
- 依赖管理核心:vue/src/core/observer/dep.js
- 观察者实现:vue/src/core/observer/watcher.js
- 调度器:vue/src/core/observer/scheduler.js
- 数据劫持:vue/src/core/observer/index.js
【免费下载链接】vue-analysis :thumbsup: Vue.js 源码分析 项目地址: https://gitcode.com/gh_mirrors/vu/vue-analysis
更多推荐
所有评论(0)