Vue依赖收集机制

2025-07-09
13335 分钟
...

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

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的依赖收集实现

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

20e264c52ab30bfcf93d7c9fcf34fd29.png

5.2 计算属性和侦听器

计算属性:

computed: {
  fullName() {
    return this.firstName + ' ' + this.lastName;
  }
}

实现逻辑:

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

侦听器:

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

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

随着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 等)都会被 自动追踪 。 当这些依赖的响应式数据发生变化时,回调会重新执行。 简单理解: 能“自动发现”它用到的响应式变量,不需要手动指定依赖。 二、基本用法 特点: 第一次立即执行...
学习面试

评论

请登录后发表评论

去登录
加载评论中...

目录