依赖收集是Vue响应式核心机制:数据被读取时,将当前Watcher记入该数据专属Dep;数据变更时,仅通知Dep中登记的Watcher更新。每个响应式属性独享Dep,组件、computed、watch各持独立Watcher,未读取则不订阅,故新增属性或数组索引操作需特殊处理。

Vue响应式系统里的“依赖收集”,说白了就是:**谁用了这个数据,Vue就悄悄记下来;等数据一变,就只通知这些“用过它”的地方去更新**。不是全量刷新,而是精准喊人。
数据访问时悄悄记下“谁在看”
Vue初始化时,会把data里每个属性用Object.defineProperty(Vue 2)或Proxy(Vue 3)包一层。当你在模板里写{{ message }},或者在计算属性、watch里读this.message,就会触发它的getter。
这时Vue干了一件事:
- 当前正有一个“观察者”(Watcher)在干活(比如正在渲染组件);
- Vue就把这个Watcher,塞进message专属的“小本本”(Dep实例)里存着。
这个“小本本”不共享,每个响应式属性都有自己的Dep,里面装的全是依赖它的Watcher。
每个组件背后都站着一个Watcher
你每定义一个组件,Vue就自动给它配一个渲染Watcher。这个Watcher在首次渲染时,会逐个读取data、computed、props里的值——这一读,就触发了对应属性的getter,从而完成依赖收集。
立即学习“前端免费学习笔记(深入)”;
递归分析 Vue 项目组件依赖,从入口文件生成组件层级图,支持 Vue 2/3,输出组件名、文件路径和属性。适用于分析组件结构、排查依赖或了解项目架构。
比如一个组件模板里用了msg和count,那这两个属性的Dep里,都会多记一笔:“这个组件的Watcher在盯我”。
同理,一个computed属性也会有自己的Watcher,它只关心自己用到的数据;一个watch: { name() {...} }也会创建独立Watcher,只订阅name的变化。
变化发生时,按名册点名通知
当你执行this.msg = 'hello',就触发setter。这时Vue不做别的,直接翻出msg那个“小本本”(Dep),挨个调用里面所有Watcher的update()方法。
这些Watcher收到通知后:
- 渲染Watcher会重新跑一遍render函数,比对新旧虚拟DOM,局部更新真实DOM;
- 计算属性Watcher会重新求值,并触发依赖它的其他响应式逻辑;
- watch的Watcher则执行你写的回调函数。
没被记在本子上的组件或逻辑,完全不受打扰——这就是高效的关键。
为什么数组或深层对象有时不响应?
因为依赖收集只发生在“被读取”的那一刻。如果某个属性压根没在模板或JS逻辑里出现过,它的getter就没被触发,自然不会被任何Watcher订阅。
另外:
- Vue 2 无法侦测直接通过索引赋值(arr[0] = x)或设置length,因为没走setter;
- 对象新增属性(obj.newKey = val)也不会触发收集,除非用Vue.set或this.$set——它会手动把新属性变成响应式,并让当前Watcher补上依赖;
- Vue 3 的Proxy能拦截in、delete、数组索引操作等,覆盖更全,但“没读过=没订阅”这条原则依然成立。

















