Vue.js 源码分析:依赖收集过程中的 targetStack 设计

【免费下载链接】vue-analysis :thumbsup: Vue.js 源码分析 【免费下载链接】vue-analysis 项目地址: https://gitcode.com/gh_mirrors/vu/vue-analysis

在 Vue.js 的响应式系统中,依赖收集是实现数据驱动视图的核心机制。当组件渲染或计算属性求值时,Vue 需要精确追踪哪些数据被使用,以便在数据变化时能触发相应的更新。targetStack 设计正是为解决嵌套依赖收集问题而诞生的关键实现,它确保了在复杂场景下依赖关系的准确性。

依赖收集的基本流程

Vue 的依赖收集基于发布-订阅模式,主要涉及三个核心角色:

当组件渲染时,会创建一个渲染 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. 依赖收集流程

mermaid

典型应用场景

1. 计算属性嵌套

当计算属性 A 依赖计算属性 B 时,targetStack 会依次保存 A 和 B 的 Watcher:

computed: {
  a() { return this.b + 1 },
  b() { return this.c * 2 }
}

执行顺序:

  1. 渲染 Watcher 入栈(Dep.target = 渲染 Watcher)
  2. 访问 a → 计算属性 A 的 Watcher 入栈(Dep.target = A)
  3. 访问 b → 计算属性 B 的 Watcher 入栈(Dep.target = B)
  4. 访问 c → B 收集 c 的依赖
  5. B 出栈 → Dep.target 恢复为 A
  6. A 收集 b 的依赖
  7. A 出栈 → Dep.target 恢复为渲染 Watcher
  8. 渲染 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 进行批量更新,避免重复渲染。

设计亮点与最佳实践

  1. 栈结构的天然适配:JavaScript 执行栈与 targetStack 的嵌套模型高度契合,保证了上下文切换的准确性
  2. 最小侵入性:通过 push/pop 封装上下文管理,不影响 Watcher 和 Dep 的核心逻辑
  3. 性能优化:避免了重复的依赖收集,确保每轮渲染只收集当前上下文所需的依赖

总结

targetStack 作为 Vue 响应式系统的上下文管理中枢,通过简单而高效的栈结构,解决了复杂场景下的依赖收集冲突问题。这种设计不仅保证了数据驱动的准确性,也为 Vue 的计算属性、嵌套 Watcher 等高级特性提供了底层支撑。理解其实现原理,有助于开发者深入掌握 Vue 的响应式机制,编写更高效的响应式代码。

相关模块完整路径:

【免费下载链接】vue-analysis :thumbsup: Vue.js 源码分析 【免费下载链接】vue-analysis 项目地址: https://gitcode.com/gh_mirrors/vu/vue-analysis

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐