深入理解 Vue 3 响应式系统原理:Proxy、Track 与 Trigger 的协奏曲

深入理解 Vue 3 响应式系统原理:Proxy、Track 与 Trigger 的协奏曲

Vue 3 的响应式系统从 Vue 2 的 Object.defineProperty 升级为基于 Proxy 的全新实现,带来了更强的性能和更高的灵活性。本文将深入剖析其底层原理,解构响应式的关键组成部分,并辅以代码和图示,带你看懂 Vue 3 的响应式魔法。


一、引言:从 Vue 2 到 Vue 3 的响应式变革

Vue 2 使用 Object.defineProperty 拦截对象属性读写,但存在如下限制:

  • 不能监听数组索引和 length 的变化;
  • 不能动态添加属性;
  • 深层嵌套对象递归成本高。

Vue 3 使用 Proxy 解决了这些痛点,实现了更强大、灵活的响应式系统。


二、Vue 3 响应式系统架构图

graph TD A[reactive包裹的对象] --> B[Proxy 对象] B --> C[getter -> track 收集依赖] B --> D[setter -> trigger 派发更新] C --> E[activeEffect 注册副作用] D --> F[effect 重新执行 -> 更新视图]

三、Proxy 是如何劫持对象的?Reflect 有何作用?

Vue 3 利用 Proxy 对原始对象进行拦截,监听属性访问和修改操作:

ts 复制代码
const handler = {
  get(target, key, receiver) {
    // 依赖收集
    track(target, key);
    const result = Reflect.get(target, key, receiver);
    return typeof result === 'object' ? reactive(result) : result;
  },
  set(target, key, value, receiver) {
    const oldValue = target[key];
    const result = Reflect.set(target, key, value, receiver);
    if (oldValue !== value) {
      // 触发更新
      trigger(target, key);
    }
    return result;
  }
};

function reactive(target) {
  return new Proxy(target, handler);
}

Proxy 拦截职责:

  • get:用于依赖收集(track)
  • set:用于触发依赖(trigger)

Reflect 的作用:

  • 提供更规范的原始操作执行方式
  • 返回布尔值表示操作是否成功
  • 避免直接访问原对象,支持继承关系和代理转发

四、track 与 trigger:响应式的灵魂

Vue 响应式系统核心在于:依赖收集(track)依赖触发(trigger)

1. 依赖收集 track

ts 复制代码
let activeEffect = null;

function track(target, key) {
  if (!activeEffect) return;
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    depsMap = new Map();
    targetMap.set(target, depsMap);
  }
  let deps = depsMap.get(key);
  if (!deps) {
    deps = new Set();
    depsMap.set(key, deps);
  }
  deps.add(activeEffect);
}

每次读取属性时,当前副作用函数(effect)会作为依赖被存储在 targetMap 中,数据结构如下:

ts 复制代码
// WeakMap<target, Map<key, Set<effects>>>
targetMap = {
  obj: {
    name: Set(effect1, effect2),
    age: Set(effect3)
  }
}

2. 依赖触发 trigger

ts 复制代码
function trigger(target, key) {
  const depsMap = targetMap.get(target);
  if (!depsMap) return;
  const deps = depsMap.get(key);
  if (deps) {
    deps.forEach(effect => effect());
  }
}

当属性变化时,所有依赖这个属性的 effect 函数将被重新执行。


五、effect 是什么?响应式如何与副作用函数关联?

effect 是用来注册副作用函数的机制。它将副作用函数与响应式数据绑定起来:

ts 复制代码
function effect(fn) {
  const run = () => {
    activeEffect = run;
    fn();
    activeEffect = null;
  };
  run();
}

例如:

ts 复制代码
const state = reactive({ count: 0 });

effect(() => {
  console.log(`count changed: ${state.count}`);
});

上述代码中,state.count 被读取,因此 console.log 所在的 effect 被收集。当 count 改变时,effect 会重新执行,自动更新输出。


六、如何避免重复收集?如何处理嵌套对象?

1. 避免重复收集

使用 Set 去重,保证同一个 effect 只被收集一次:

ts 复制代码
deps.add(activeEffect); // Set 会自动去重

2. 嵌套对象自动递归响应式

get 中递归调用 reactive()

ts 复制代码
const result = Reflect.get(target, key, receiver);
return typeof result === 'object' && result !== null
  ? reactive(result)
  : result;

这确保即使嵌套对象尚未访问,也能在访问时被转为响应式。

例如:

ts 复制代码
const state = reactive({
  user: {
    name: 'Heart',
    address: {
      city: 'guangzhou'
    }
  }
});

只有当你访问 state.user.address.city 时,对应层级才会被递归地变成 Proxy。


七、小结:Vue 响应式的设计与优势

特性 Vue 2 Vue 3
实现方式 Object.defineProperty Proxy
动态属性 不支持 支持
数组监听 局限较多 完善支持
嵌套对象处理 初始化递归 访问时递归,懒代理
性能 响应式递归开销大 更加高效、灵活

Vue 3 的响应式机制不仅解决了 Vue 2 的痛点,还借助现代 JavaScript 特性构建了一个轻量而强大的响应式系统,是前端响应式范式演进的重要一环。


💡 延伸阅读

相关推荐
RadiumAg29 分钟前
记一道有趣的面试题
前端·javascript
yangzhi_emo33 分钟前
ES6笔记2
开发语言·前端·javascript
yanlele1 小时前
我用爬虫抓取了 25 年 5 月掘金热门面试文章
前端·javascript·面试
中微子2 小时前
React状态管理最佳实践
前端
烛阴2 小时前
void 0 的奥秘:解锁 JavaScript 中 undefined 的正确打开方式
前端·javascript
中微子2 小时前
JavaScript 事件与 React 合成事件完全指南:从入门到精通
前端
Hexene...2 小时前
【前端Vue】如何实现echarts图表根据父元素宽度自适应大小
前端·vue.js·echarts
天天扭码3 小时前
《很全面的前端面试题》——HTML篇
前端·面试·html
xw53 小时前
我犯了错,我于是为我的uni-app项目引入环境标志
前端·uni-app
!win !3 小时前
被老板怼后,我为uni-app项目引入环境标志
前端·小程序·uni-app