Dep是Vue响应式系统中负责依赖收集与通知的核心枢纽,它通过subs数组记录关心某数据的Watcher,并在数据变更时调用notify()精准通知所有相关Watcher。

Vue响应式系统中,Dep 是依赖管理的核心枢纽,它不直接监听数据,也不执行更新逻辑,而是专注做一件事:**记录谁关心哪个数据、并在数据变化时精准通知所有相关方**。它的存在,让 Observer 和 Watcher 能解耦协作——Observer 只管“我变了”,Watcher 只管“我该做什么”,中间靠 Dep 来牵线。
Dep 是什么:一个带“通讯录”的发布中心
Dep(Dependency 的缩写)本质是一个依赖容器,内部维护一个 subs 数组,里面存的全是当前属性所关联的 Watcher 实例。它本身没有状态感知能力,但有两个关键动作:
-
depend():当某个属性被读取(比如模板渲染或 computed 计算时触发 getter),Dep 就会检查全局静态变量
Dep.target是否存在;如果存在(通常就是当前正在求值的 Watcher),就调用target.addDep(this),把自己加进该 Watcher 的依赖列表,同时让 Watcher 把自己加入subs—— 这就是“依赖收集”。 -
notify():当属性被修改(setter 触发),Dep 遍历
subs,对每个 Watcher 调用其update()方法 —— 这就是“派发通知”。
Watcher 怎么成为“订阅者”:靠 Dep.target 建立临时通道
Watcher 在实例化时,会主动把自身赋值给 Dep.target(通过 pushTarget(this)),然后去访问响应式数据(如 this.message)。此时数据的 getter 被触发,自动执行 dep.depend(),完成双向绑定:Watcher 记住 dep,dep 也记住 Watcher。
这个过程是临时且受控的:
Vue 3 组合式 API 与 最佳实践,涵盖代码组织、useXxx 模式、组合式函数设计、Store 集成、响应式优化及功能抽取。
立即学习“前端免费学习笔记(深入)”;
- 每个 Watcher 求值前设
Dep.target = this,求值后立即清空(popTarget()),避免嵌套或重复收集; - 用栈(
targetStack)管理多层 Watcher(比如父组件渲染中触发子组件 computed),确保退出时能恢复上一层上下文; - 一个数据属性(如
message)可能被多个 Watcher 用到:模板渲染、watch 监听、computed 计算 —— 它们的 Watcher 全部会被收集进同一个 Dep 的subs数组。
一次完整的“读→改→更”流程图解
以 data: { message: 'hello' } 和 <p>{{ message }}</p> 为例:
-
初始化渲染:创建渲染 Watcher → 设
Dep.target = watcher→ 访问message→ 触发 getter →dep.depend()→ Watcher 加入 dep.subs; -
用户修改:执行
this.message = 'hi'→ 触发 setter →dep.notify()→ 遍历 subs → 每个 Watcher 执行update()(触发 re-render); -
若还有 watch:
watch: { message() { ... } }也会在初始化时新建一个 Watcher,并同样被收集进message对应的 Dep 中,共享同一份通知机制。
为什么数组需要特殊处理
对象属性可通过 Object.defineProperty 对每个 key 劫持 getter/setter,但数组方法(如 push、splice)不触发 setter。Vue 2 的解法是:
- 对数组原型方法(
push、pop等 7 个)做拦截重写,在调用原方法后手动触发dep.notify(); - 数组元素本身仍是普通 JS 对象,若为响应式对象,其内部属性仍走标准 getter/setter 收集;
- 数组的 Dep 不是挂在每个索引上,而是整个数组实例(
__ob__.dep)拥有一个 Dep,用于通知“数组结构变更”。
这套机制看似绕,实则精巧:用最轻量的静态标识(Dep.target)实现运行时依赖映射,靠闭包和引用维持关系,不侵入业务逻辑。Vue 3 改用 Proxy 后,Dep 的角色未变,只是收集方式更自然——但核心思想始终如一:数据是源头,Watcher 是终端,Dep 是不可见却必不可少的调度台。

















