Vue依赖收集机制
2025-07-09
1333 字约 5 分钟
...Vue响应式系统的核心问题
当我们修改Vue组件中的数据时,视图会自动更新——这看似神奇的背后,是Vue强大的响应式系统在发挥作用。而这个系统的核心,正是依赖收集机制。本文将深入探索Vue是如何追踪数据变化并高效更新DOM的。
<div class="vue-dependency-illustration">
<div class="data-property">数据属性</div>
<div class="dependency-collector">依赖收集器</div>
<div class="watcher">观察者(Watcher)</div>
<div class="component">Vue组件</div>
<div class="connections">
<div class="conn conn1"></div>
<div class="conn conn2"></div>
<div class="conn conn3"></div>
<div class="conn conn4"></div>
<div class="conn conn5"></div>
</div>
</div>
1. Vue响应式基础:三个核心概念
1.1 响应式数据对象(Reactive Data)
Vue通过Object.defineProperty(Vue 2)或Proxy(Vue 3)将普通JS对象转换为响应式对象:
function defineReactive(obj, key) {
let value = obj[key];
const dep = new Dep();
Object.defineProperty(obj, key, {
get() {
if (Dep.target) {
dep.depend();
}
return value;
},
set(newVal) {
if (newVal === value) return;
value = newVal;
dep.notify();
}
});
}
1.2 依赖收集器(Dep)
每个响应式属性都有一个Dep实例,负责管理所有依赖该属性的"订阅者"(Watcher):
class Dep {
constructor() {
this.subs = [];
}
addSub(sub) {
this.subs.push(sub);
}
removeSub(sub) {
remove(this.subs, sub);
}
depend() {
if (Dep.target) {
Dep.target.addDep(this);
}
}
notify() {
const subs = this.subs.slice();
for (let i = 0; i < subs.length; i++) {
subs[i].update();
}
}
}
Dep.target = null;
1.3 观察者(Watcher)
Watcher是Vue中观察数据变化的实体,代表一个可执行单元(如组件渲染函数、计算属性等):
class Watcher {
constructor(vm, expOrFn) {
this.vm = vm;
this.getter = expOrFn;
this.deps = [];
this.value = this.get();
}
get() {
pushTarget(this);
let value;
try {
value = this.getter.call(this.vm);
} finally {
popTarget();
}
return value;
}
addDep(dep) {
if (!this.deps.includes(dep)) {
this.deps.push(dep);
dep.addSub(this);
}
}
update() {
queueWatcher(this);
}
run() {
const value = this.get();
if (value !== this.value) {
this.value = value;
}
}
}
const targetStack = [];
function pushTarget(target) {
targetStack.push(target);
Dep.target = target;
}
function popTarget() {
targetStack.pop();
Dep.target = targetStack[targetStack.length - 1];
}
2. 依赖收集的完整流程
2.1 初始化阶段

fba01c87ac96fc5559dad35a8552461b.png
2.2 依赖收集阶段

739ecdba6fbfb58a49d3db2f005eeec9.png
739ecdba6fbfb58a49d3db2f005eeec9.png
2.3 更新触发阶段

146fd45761b4e22eb8545b384dc87696.png
3. Vue 2 vs Vue 3实现差异
| 特性 | Vue 2 | Vue 3 |
|---|---|---|
| 核心API | Object.defineProperty | Proxy |
| 数组支持 | 需要特殊处理 | 原生支持 |
| 新增属性 | Vue.set/vm.$set | 原生响应 |
| 性能 | 递归对象属性 | 惰性处理 |
| 依赖收集 | 基于属性的Dep | 基于Proxy的track/trigger |
Vue 3的依赖收集实现
const targetMap = new WeakMap();
function track(target, type, key) {
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}
if (activeEffect) {
dep.add(activeEffect);
activeEffect.deps.push(dep);
}
}
function trigger(target, type, key) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
const effects = new Set();
depsMap.get(key).forEach(effect => {
effects.add(effect);
});
effects.forEach(effect => {
if (effect.options.scheduler) {
effect.options.scheduler(effect);
} else {
effect();
}
});
}
const reactive = (target) => {
return new Proxy(target, {
get(target, key, receiver) {
track(target, 'get', key);
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
const oldValue = target[key];
const result = Reflect.set(target, key, value, receiver);
if (oldValue !== value) {
trigger(target, 'set', key);
}
return result;
}
});
};
4. 关键设计细节解析
4.1 异步更新队列
Vue使用nextTick实现异步更新:
const queue = [];
let waiting = false;
function queueWatcher(watcher) {
if (queue.includes(watcher)) return;
queue.push(watcher);
if (!waiting) {
waiting = true;
nextTick(flushSchedulerQueue);
}
}
function flushSchedulerQueue() {
const watchers = queue.slice().sort();
queue.length = 0;
for (let i = 0; i < watchers.length; i++) {
watchers[i].run();
}
waiting = false;
}
4.2 依赖清除机制
当组件销毁或计算属性重新计算时,需要清理旧依赖:
class Watcher {
constructor() {
}
teardown() {
for (let i = this.deps.length - 1; i >= 0; i--) {
this.deps[i].removeSub(this);
}
this.deps = [];
}
get() {
this.cleanupDeps();
}
cleanupDeps() {
for (let i = 0; i < this.deps.length; i++) {
const dep = this.deps[i];
if (!this.newDeps.has(dep)) {
dep.removeSub(this);
}
}
let tmp = this.deps;
this.deps = this.newDeps;
this.newDeps = tmp;
this.newDeps.clear();
}
}
5. 依赖收集在Vue生态系统中的应用
5.1 组件渲染系统

20e264c52ab30bfcf93d7c9fcf34fd29.png
5.2 计算属性和侦听器
计算属性:
computed: {
fullName() {
return this.firstName + ' ' + this.lastName;
}
}
实现逻辑:
- 为计算属性创建计算Watcher
- 计算时访问的依赖项会被收集
- 依赖变化时重新计算,但只在实际使用的地方重新执行(惰性计算)
侦听器:
watch: {
'person.age': function(newVal, oldVal) {
console.log('Age changed');
}
}
实现特点:
- 为每个侦听属性创建UserWatcher
- 支持deep选项:递归遍历对象属性
- 支持immediate选项:立即执行一次
6. 性能优化策略
6.1 避免不必要的响应式
this.config = Object.freeze({ apiUrl: 'https://...' });
this.bigData = { }
this.dataPart1 = { }
this.dataPart2 = { }
6.2 合理使用v-once
<div v-once>公司名称:{{ companyName }}</div>
6.3 优化Watcher创建
Vue.component('functional-comp', {
functional: true,
render(h, ctx) {
return h('div', ctx.props.data);
}
})
<!-- 不推荐 -->
<div>{{ expensiveComputation() }}</div>
<!-- 推荐 -->
<div>{{ computedResult }}</div>
6.4 优化列表渲染
<li v-for="item in items" :key="item.id">{{ item.text }}</li>
<li v-for="item in items" v-if="item.active"></li>
<li v-for="item in activeItems"></li>
7. 实战:依赖收集可视化调试
<div id="dependency-debug">
<button @click="addDep">添加依赖</button>
<div class="dependencies">
<div v-for="(dep, key) in depsInfo" class="dep-item">
<div class="key">{{ key }}: {{ dep.value }}</div>
<div class="subs">
<span v-for="(sub, i) in dep.subs" :key="i" class="watcher-badge">Watcher-{{ i+1 }}</span>
</div>
</div>
</div>
</div>
<script>
function createDepTracker(Vue) {
const originalDefineReactive = Vue.util.defineReactive;
Vue.util.defineReactive = function(obj, key, val) {
const dep = new Vue.util.Dep();
if (!obj.__deps) obj.__deps = {};
obj.__deps[key] = dep;
const getter = () => {
if (Dep.target) {
dep.depend();
}
return val;
};
const setter = (newVal) => {
val = newVal;
dep.notify();
};
Object.defineProperty(obj, key, {
get: getter,
set: setter
});
};
return {
getDeps(instance) {
return instance._data.__deps || {};
}
};
}
</script>
小结
Vue的依赖收集机制是一个精妙的设计:
- 自动追踪:无需手动声明依赖关系
- 高效更新:精确到属性的更新通知
- 内存优化:自动清理无效依赖
- 跨平台支持:适配不同渲染目标
随着Vue 3的推出,依赖收集系统得到了进一步优化:
- 基于Proxy的新响应式系统
- 更细粒度的跟踪
- 更高效的组件更新策略
如果您觉得这篇文章有帮助,请点个赞吧~
相关文章
更多文章 →vue2025-10-22
shallowRef 与 ref 的区别、场景与坑位
结论先行: 会 深度 地把你放进去的对象转成响应式(递归代理),因此"对象内部的改动"也会触发视图更新。 只在 顶层 做依赖追踪: 更换 才触发更新 ;若你只是"在原对象上改属性",不会触发,除非手动 。 1\. 快速对比 维度 追踪粒度 深度(递归) 仅顶层 对象内部属性变更 会触发更新 不会 触发(除非 或重新赋值) 性能 有递归 & 依赖跟踪开销 更轻量,适合大对象/第三方实例 适用对象 普通标量、普通对象、数组 第三方实例(图表...
学习面试
vue2025-10-20
Vue 3 中的 setup执行时机与异步逻辑详解
Vue 3 中的 执行时机与异步逻辑详解 本文详细介绍 Vue 3 组合式 API 的核心函数 :它在组件生命周期中的执行时机、异步行为( 的影响)、常见用法和最佳实践。 一、 的执行时机 是 Vue 3 组件实例创建后、渲染前执行的函数。 它相当于 Vue 2 中的 和 的合体。 执行流程: 也就是说: 所有响应式数据( 、 、 )都在这里定义; 所有生命周期钩子( 、 等)都在这里注册; 模板渲染会等待 执行完成(或异步返回的 Pr...
学习面试
vue2025-10-08
vue中ref与reactive
Vue 3 的 与 :区别、实现,以及为什么更推荐 1\. 一句话结论 :适合 原始类型 或 独立、细粒度 的状态单元;类型推断明确、解构安全、追踪粒度更小, 默认首选 。 :适合管理 对象/集合 这类结构化状态;但 解构会丢失响应性 , 默认 非深度 ,需要正确的使用姿势配合。 2\. 用法与语义差异 2.1 基本示例 2.2 模板中的解包(unwrapping) 在 模板 里, 会 自动解包 ,可直接写 ,不必 。 在响应式对象中...
学习面试
vue2025-09-23
Vue3 中computed属性依赖总结
Vue3 属性依赖总结 1. 依赖收集机制 的 getter 内部 读取 到哪些响应式变量 ( / ),就会把这些变量收集为依赖。 当这些依赖发生变化时, 会重新计算。 2. 如果只返回常量 始终是 。 修改 或 不会触发 重新计算,因为 getter 内没有访问它们。 3. 如果访问了响应式变量但仍返回常量 初始化时会打印一次。 当 或 改变时,getter 会重新执行(再次打印),因为它们被收集为依赖。 但 依旧始终是 。 4. 总...
学习面试
vue2025-09-17
template标签为什么不可以使用v-show
前言 写 Vue 时,最容易踩的小坑之一,就是给 套一个 。编辑器不报错,页面却“消失”了。很多人以为这是 bug,其实它只是 Vue 的底层设计在默默提醒: 不是真节点,它只是个“草稿纸”。 一、为什么 拒绝 v show 的本质是切换元素的 CSS: 。浏览器要找到真实 DOM 节点,才能给它加样式。而 在运行时会被完全剥离,不会留下任何标签,Vue 找不到节点,也就无处下刀。 举个反面教材: 打开控制台,你会看到 还是生成了,但...
学习面试
vue2025-09-17
Vue 3中watchEffect自动追踪详解
Vue 3 自动追踪详解 一、是什么 是 Vue 3 Composition API 提供的一个响应式副作用(side effect)函数。 它会 立即执行一次回调函数 ,并且在回调函数中访问到的所有响应式数据(ref、reactive、computed 等)都会被 自动追踪 。 当这些依赖的响应式数据发生变化时,回调会重新执行。 简单理解: 能“自动发现”它用到的响应式变量,不需要手动指定依赖。 二、基本用法 特点: 第一次立即执行...
学习面试
评论
请登录后发表评论
去登录