Computed 依赖追踪本质是运行时通过 getter 执行中访问响应式数据触发 get 拦截,自动将当前计算 effect 记入对应属性的依赖列表;变更时标记 dirty,读取时惰性重算。

Computed 的依赖追踪,本质是 Vue 响应式系统在 getter 执行过程中“悄悄记下它碰了哪些响应式数据”。不是靠语法分析、也不是靠手动声明,而是在运行时通过 访问拦截 + 依赖记录 实现的。
依赖是怎么被“自动收集”的?
当你定义一个 computed:
const fullName = computed(() => firstName.value + lastName.value)
Vue 并不会立刻执行这个函数。而是等到你第一次读取 fullName.value 时,才真正调用该函数。就在这个执行瞬间,发生了关键动作:
- firstName.value 被读取 → 触发 ref 的 get 拦截器 → 此时 Vue 知道“当前正在计算 fullName”,就把 firstName 和当前计算属性(fullName 的内部 effect)关联起来
- lastName.value 被读取 → 同样触发 get 拦截 → 继续把 lastName 也记为 fullName 的依赖
- 函数执行完毕,依赖收集完成,结果缓存,effect 标记为“已激活”
底层靠什么实现“访问即追踪”?
Vue3 使用 Proxy 包裹响应式对象(ref/reactive),所有属性读写都经过拦截:
- 对 ref 的 .value 读取,走的是 ref 内部的 reactive 包装或基于 Proxy 的 get 拦截
- 对 reactive 对象属性的访问(如 state.name),直接由 Proxy 的 get trap 捕获
- 每次 get 触发时,如果当前存在一个正在运行的计算 effect(比如 computed 的 getter),就把它加入该属性的依赖列表(Dep)
这个过程完全透明——你不需要 import 任何辅助函数,也不用标记哪些变量要追踪。只要它们出现在 getter 函数体中并被读取,就会被自动捕获。
为什么普通变量或函数调用不会成为依赖?
因为它们不经过 Vue 的响应式拦截:
- let localName = '张三' —— 是局部变量,无 Proxy,读取不会触发任何依赖注册
- getDate() —— 是函数调用,执行本身不涉及响应式属性的 get,除非函数内部又读了 ref 或 reactive
- computed(() => obj.nonReactiveProp) —— 如果 obj 不是响应式对象,它的属性访问根本不会进 Proxy,自然无法收集依赖
依赖变化后如何通知重新计算?
当某个被追踪的响应式数据变更时(比如 firstName.value = '李四'):
- 对应 ref 或 reactive 属性的 set 拦截器被触发
- 它遍历该属性所记录的所有依赖 effect(包括 fullName 的计算 effect)
- 将 effect 标记为“脏(dirty)”,但不立即执行
- 下一次读取 fullName.value 时,发现是 dirty,才重新运行 getter、更新缓存、重置 dirty 状态
这就是“惰性求值 + 脏检查”的组合,既保证响应及时,又避免无效计算。


















