Vue响应式系统的核心问题
当我们修改Vue组件中的数据时,视图会自动更新——这看似神奇的背后,是Vue强大的响应式系统在发挥作用。而这个系统的核心,正是依赖收集机制。本文将深入探索Vue是如何追踪数据变化并高效更新DOM的。
1<div class="vue-dependency-illustration">
2 <div class="data-property">数据属性</div>
3 <div class="dependency-collector">依赖收集器</div>
4 <div class="watcher">观察者(Watcher)</div>
5 <div class="component">Vue组件</div>
6
7 <div class="connections">
8 <div class="conn conn1"></div>
9 <div class="conn conn2"></div>
10 <div class="conn conn3"></div>
11 <div class="conn conn4"></div>
12 <div class="conn conn5"></div>
13 </div>
14</div>
1. Vue响应式基础:三个核心概念
1.1 响应式数据对象(Reactive Data)
Vue通过Object.defineProperty
(Vue 2)或Proxy
(Vue 3)将普通JS对象转换为响应式对象:
1function defineReactive(obj, key) {
2 let value = obj[key];
3 const dep = new Dep();
4
5 Object.defineProperty(obj, key, {
6 get() {
7
8 if (Dep.target) {
9 dep.depend();
10 }
11 return value;
12 },
13 set(newVal) {
14 if (newVal === value) return;
15 value = newVal;
16
17 dep.notify();
18 }
19 });
20}
1.2 依赖收集器(Dep)
每个响应式属性都有一个Dep实例,负责管理所有依赖该属性的”订阅者”(Watcher):
1class Dep {
2 constructor() {
3 this.subs = [];
4 }
5
6 addSub(sub) {
7 this.subs.push(sub);
8 }
9
10 removeSub(sub) {
11 remove(this.subs, sub);
12 }
13
14 depend() {
15 if (Dep.target) {
16 Dep.target.addDep(this);
17 }
18 }
19
20 notify() {
21 const subs = this.subs.slice();
22 for (let i = 0; i < subs.length; i++) {
23 subs[i].update();
24 }
25 }
26}
27
28Dep.target = null;
1.3 观察者(Watcher)
Watcher是Vue中观察数据变化的实体,代表一个可执行单元(如组件渲染函数、计算属性等):
1class Watcher {
2 constructor(vm, expOrFn) {
3 this.vm = vm;
4 this.getter = expOrFn;
5 this.deps = [];
6 this.value = this.get();
7 }
8
9 get() {
10 pushTarget(this);
11 let value;
12 try {
13 value = this.getter.call(this.vm);
14 } finally {
15 popTarget();
16 }
17 return value;
18 }
19
20 addDep(dep) {
21 if (!this.deps.includes(dep)) {
22 this.deps.push(dep);
23 dep.addSub(this);
24 }
25 }
26
27 update() {
28 queueWatcher(this);
29 }
30
31 run() {
32 const value = this.get();
33 if (value !== this.value) {
34
35 this.value = value;
36 }
37 }
38}
39
40
41const targetStack = [];
42function pushTarget(target) {
43 targetStack.push(target);
44 Dep.target = target;
45}
46function popTarget() {
47 targetStack.pop();
48 Dep.target = targetStack[targetStack.length - 1];
49}
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的依赖收集实现
1const targetMap = new WeakMap();
2
3function track(target, type, key) {
4 let depsMap = targetMap.get(target);
5 if (!depsMap) {
6 targetMap.set(target, (depsMap = new Map()));
7 }
8 let dep = depsMap.get(key);
9 if (!dep) {
10 depsMap.set(key, (dep = new Set()));
11 }
12
13
14 if (activeEffect) {
15 dep.add(activeEffect);
16 activeEffect.deps.push(dep);
17 }
18}
19
20function trigger(target, type, key) {
21 const depsMap = targetMap.get(target);
22 if (!depsMap) return;
23
24 const effects = new Set();
25 depsMap.get(key).forEach(effect => {
26 effects.add(effect);
27 });
28
29 effects.forEach(effect => {
30 if (effect.options.scheduler) {
31 effect.options.scheduler(effect);
32 } else {
33 effect();
34 }
35 });
36}
37
38const reactive = (target) => {
39 return new Proxy(target, {
40 get(target, key, receiver) {
41 track(target, 'get', key);
42 return Reflect.get(target, key, receiver);
43 },
44 set(target, key, value, receiver) {
45 const oldValue = target[key];
46 const result = Reflect.set(target, key, value, receiver);
47 if (oldValue !== value) {
48 trigger(target, 'set', key);
49 }
50 return result;
51 }
52 });
53};
4. 关键设计细节解析
4.1 异步更新队列
Vue使用nextTick
实现异步更新:
1const queue = [];
2let waiting = false;
3
4function queueWatcher(watcher) {
5 if (queue.includes(watcher)) return;
6
7 queue.push(watcher);
8
9 if (!waiting) {
10 waiting = true;
11 nextTick(flushSchedulerQueue);
12 }
13}
14
15function flushSchedulerQueue() {
16 const watchers = queue.slice().sort();
17 queue.length = 0;
18
19 for (let i = 0; i < watchers.length; i++) {
20 watchers[i].run();
21 }
22
23 waiting = false;
24}
4.2 依赖清除机制
当组件销毁或计算属性重新计算时,需要清理旧依赖:
1class Watcher {
2 constructor() {
3
4 }
5
6 teardown() {
7
8 for (let i = this.deps.length - 1; i >= 0; i--) {
9 this.deps[i].removeSub(this);
10 }
11 this.deps = [];
12 }
13
14 get() {
15
16 this.cleanupDeps();
17
18 }
19
20 cleanupDeps() {
21 for (let i = 0; i < this.deps.length; i++) {
22 const dep = this.deps[i];
23 if (!this.newDeps.has(dep)) {
24 dep.removeSub(this);
25 }
26 }
27
28
29 let tmp = this.deps;
30 this.deps = this.newDeps;
31 this.newDeps = tmp;
32 this.newDeps.clear();
33 }
34}
5. 依赖收集在Vue生态系统中的应用
5.1 组件渲染系统
20e264c52ab30bfcf93d7c9fcf34fd29.png
5.2 计算属性和侦听器
计算属性:
1computed: {
2 fullName() {
3 return this.firstName + ' ' + this.lastName;
4 }
5}
实现逻辑:
- 为计算属性创建计算Watcher
- 计算时访问的依赖项会被收集
- 依赖变化时重新计算,但只在实际使用的地方重新执行(惰性计算)
侦听器:
1watch: {
2 'person.age': function(newVal, oldVal) {
3 console.log('Age changed');
4 }
5}
实现特点:
- 为每个侦听属性创建UserWatcher
- 支持deep选项:递归遍历对象属性
- 支持immediate选项:立即执行一次
6. 性能优化策略
6.1 避免不必要的响应式
1this.config = Object.freeze({ apiUrl: 'https://...' });
2
3
4
5this.bigData = { }
6
7
8this.dataPart1 = { }
9this.dataPart2 = { }
6.2 合理使用v-once
1<div v-once>公司名称:{{ companyName }}</div>
6.3 优化Watcher创建
1Vue.component('functional-comp', {
2 functional: true,
3 render(h, ctx) {
4
5 return h('div', ctx.props.data);
6 }
7})
8
9
10<!-- 不推荐 -->
11<div>{{ expensiveComputation() }}</div>
12
13<!-- 推荐 -->
14<div>{{ computedResult }}</div>
6.4 优化列表渲染
1<li v-for="item in items" :key="item.id">{{ item.text }}</li>
2
3
4
5<li v-for="item in items" v-if="item.active"></li>
6
7
8<li v-for="item in activeItems"></li>
7. 实战:依赖收集可视化调试
1<div id="dependency-debug">
2 <button @click="addDep">添加依赖</button>
3 <div class="dependencies">
4 <div v-for="(dep, key) in depsInfo" class="dep-item">
5 <div class="key">{{ key }}: {{ dep.value }}</div>
6 <div class="subs">
7 <span v-for="(sub, i) in dep.subs" :key="i" class="watcher-badge">Watcher-{{ i+1 }}</span>
8 </div>
9 </div>
10 </div>
11</div>
12
13<script>
14
15function createDepTracker(Vue) {
16 const originalDefineReactive = Vue.util.defineReactive;
17
18 Vue.util.defineReactive = function(obj, key, val) {
19 const dep = new Vue.util.Dep();
20
21
22 if (!obj.__deps) obj.__deps = {};
23 obj.__deps[key] = dep;
24
25 const getter = () => {
26 if (Dep.target) {
27 dep.depend();
28 }
29 return val;
30 };
31
32 const setter = (newVal) => {
33 val = newVal;
34 dep.notify();
35 };
36
37 Object.defineProperty(obj, key, {
38 get: getter,
39 set: setter
40 });
41 };
42
43 return {
44 getDeps(instance) {
45 return instance._data.__deps || {};
46 }
47 };
48}
49</script>
小结
Vue的依赖收集机制是一个精妙的设计:
- 自动追踪:无需手动声明依赖关系
- 高效更新:精确到属性的更新通知
- 内存优化:自动清理无效依赖
- 跨平台支持:适配不同渲染目标
随着Vue 3的推出,依赖收集系统得到了进一步优化:
- 基于Proxy的新响应式系统
- 更细粒度的跟踪
- 更高效的组件更新策略
个人笔记记录 2021 ~ 2025