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

fba01c87ac96fc5559dad35a8552461b.png

2.2 依赖收集阶段

 

 

739ecdba6fbfb58a49d3db2f005eeec9.png

739ecdba6fbfb58a49d3db2f005eeec9.png

 

739ecdba6fbfb58a49d3db2f005eeec9.png

2.3 更新触发阶段

 

 

146fd45761b4e22eb8545b384dc87696.png

146fd45761b4e22eb8545b384dc87696.png

3. Vue 2 vs Vue 3实现差异

特性Vue 2Vue 3
核心APIObject.definePropertyProxy
数组支持需要特殊处理原生支持
新增属性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

20e264c52ab30bfcf93d7c9fcf34fd29.png

5.2 计算属性和侦听器

计算属性:

 1computed: {
 2  fullName() {
 3    return this.firstName + ' ' + this.lastName;
 4  }
 5}

实现逻辑:

  1. 为计算属性创建计算Watcher
  2. 计算时访问的依赖项会被收集
  3. 依赖变化时重新计算,但只在实际使用的地方重新执行(惰性计算)

侦听器:

 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的依赖收集机制是一个精妙的设计:

  1. 自动追踪:无需手动声明依赖关系
  2. 高效更新:精确到属性的更新通知
  3. 内存优化:自动清理无效依赖
  4. 跨平台支持:适配不同渲染目标

随着Vue 3的推出,依赖收集系统得到了进一步优化:

  • 基于Proxy的新响应式系统
  • 更细粒度的跟踪
  • 更高效的组件更新策略
个人笔记记录 2021 ~ 2025