深入理解 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 特性构建了一个轻量而强大的响应式系统,是前端响应式范式演进的重要一环。


💡 延伸阅读

相关推荐
Fantastic_sj2 小时前
CSS-in-JS 动态主题切换与首屏渲染优化
前端·javascript·css
鹦鹉0072 小时前
SpringAOP实现
java·服务器·前端·spring
再学一点就睡5 小时前
手写 Promise 静态方法:从原理到实现
前端·javascript·面试
再学一点就睡6 小时前
前端必会:Promise 全解析,从原理到实战
前端·javascript·面试
前端工作日常6 小时前
我理解的eslint配置
前端·eslint
前端工作日常7 小时前
项目价值判断的核心标准
前端·程序员
90后的晨仔7 小时前
理解 Vue 的列表渲染:从传统 DOM 到响应式世界的演进
前端·vue.js
OEC小胖胖8 小时前
性能优化(一):时间分片(Time Slicing):让你的应用在高负载下“永不卡顿”的秘密
前端·javascript·性能优化·web
烛阴8 小时前
ABS - Rhomb
前端·webgl
植物系青年8 小时前
10+核心功能点!低代码平台实现不完全指南 🧭(下)
前端·低代码