news 2026/6/14 20:53:02

Vue3 响应式原理拆解:从 Proxy 代理到依赖收集的完整链路

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Vue3 响应式原理拆解:从 Proxy 代理到依赖收集的完整链路

Vue3 响应式原理拆解:从 Proxy 代理到依赖收集的完整链路

一、Vue2 响应式的局限:Object.defineProperty 的三个盲区

Vue2 使用 Object.defineProperty 实现响应式,但这个 API 有三个无法绕过的局限:第一,无法检测属性的新增和删除(obj.newProp = value不触发更新);第二,无法检测数组索引的直接赋值(arr[0] = newVal不触发更新);第三,深度监听需要在初始化时递归遍历所有属性,对大型对象的初始化性能影响显著。

Vue3 用 Proxy 替代 Object.defineProperty,从根本上解决了这三个问题。Proxy 可以拦截对象的所有操作(包括属性新增、删除和数组索引修改),且深度监听是惰性的——只有被访问的属性才会被代理,未访问的属性不会触发任何开销。

二、Vue3 响应式系统的核心机制

Vue3 响应式系统的核心是三个概念的协作:响应式代理(Proxy)、依赖收集(Effect)和调度更新(Scheduler)。

flowchart TB READ[读取属性 obj.name] --> TRACK[依赖收集 track] TRACK --> EFF[记录当前 Effect] EFF --> MAP[targetMap] WRITE[写入属性 obj.name = 'new'] --> TRIGGER[触发更新 trigger] TRIGGER --> MAP MAP --> RUN[执行相关 Effects] RUN --> SCHED[Scheduler 调度] SCHED --> FLUSH[批量刷新 DOM] subgraph 响应式代理 Proxy READ WRITE end subgraph 依赖收集 TRACK EFF MAP[targetMap WeakMap] end subgraph 调度更新 TRIGGER RUN SCHED FLUSH end

Proxy 拦截:通过get拦截器实现依赖收集(谁读取了这个属性),通过set拦截器实现触发更新(通知所有依赖这个属性的 Effect 重新执行)。

依赖收集:使用全局的targetMap(WeakMap)存储依赖关系。结构是targetMap → target → Map(key → Set(effect))。当 Effect 执行时读取某个属性,这个 Effect 就被记录到该属性的依赖集合中。

调度更新:多个属性同时变更时,对应的 Effect 不应该立即执行,而是被收集到队列中,在下一个微任务中批量执行。这避免了同一个 Effect 在一次事件循环中被多次执行。

三、Vue3 响应式核心的简化实现

/** * Vue3 响应式系统简化实现 * 核心三件套:reactive / effect / computed */ // --- 全局状态 --- let activeEffect: ReactiveEffect | null = null; const targetMap = new WeakMap<object, Map<string | symbol, Set<ReactiveEffect>>>(); // --- Effect --- class ReactiveEffect { private _fn: () => void; private _scheduler?: () => void; deps: Set<ReactiveEffect>[] = []; // 该 Effect 被哪些依赖集合引用 constructor(fn: () => void, scheduler?: () => void) { this._fn = fn; this._scheduler = scheduler; } run() { // 设置全局 activeEffect,使依赖收集能获取当前 Effect activeEffect = this; try { return this._fn(); } finally { // 执行完毕后清除,避免后续无关操作误收集 activeEffect = null; } } stop() { // 从所有依赖集合中移除该 Effect,使其不再被触发 for (const dep of this.deps) { dep.delete(this); } } } // --- 依赖收集与触发 --- function track(target: object, key: string | symbol) { if (!activeEffect) return; // 不在 Effect 上下文中,无需收集 let targetDeps = targetMap.get(target); if (!targetDeps) { targetDeps = new Map(); targetMap.set(target, targetDeps); } let keyDeps = targetDeps.get(key); if (!keyDeps) { keyDeps = new Set(); targetDeps.set(key, keyDeps); } if (!keyDeps.has(activeEffect)) { keyDeps.add(activeEffect); // 反向引用:Effect 记录自己被哪些集合引用,便于 stop 时清理 activeEffect.deps.push(keyDeps); } } function trigger(target: object, key: string | symbol) { const targetDeps = targetMap.get(target); if (!targetDeps) return; const keyDeps = targetDeps.get(key); if (!keyDeps) return; // 创建副本遍历,避免 Effect 执行过程中修改集合 const effectsToRun = new Set(keyDeps); for (const effect of effectsToRun) { // 有 scheduler 则走调度(异步批量),否则同步执行 if (effect._scheduler) { effect._scheduler(); } else { effect.run(); } } } // --- reactive --- const reactiveMap = new WeakMap<object, any>(); function reactive<T extends object>(target: T): T { if (reactiveMap.has(target)) return reactiveMap.get(target); const proxy = new Proxy(target, { get(target, key, receiver) { // 依赖收集:谁读取了这个属性 track(target, key); const result = Reflect.get(target, key, receiver); // 惰性深度代理:只有被访问的嵌套对象才会被代理 if (result !== null && typeof result === "object") { return reactive(result); } return result; }, set(target, key, value, receiver) { const oldValue = Reflect.get(target, key, receiver); const result = Reflect.set(target, key, value, receiver); // 只在值真正变化时触发更新,避免无意义渲染 if (oldValue !== value) { trigger(target, key); } return result; }, deleteProperty(target, key) { const hadKey = Reflect.has(target, key); const result = Reflect.deleteProperty(target, key); if (hadKey && result) { trigger(target, key); // 属性删除也触发更新 } return result; }, }); reactiveMap.set(target, proxy); return proxy; } // --- effect --- function effect(fn: () => void, options?: { scheduler?: () => void }) { const _effect = new ReactiveEffect(fn, options?.scheduler); // 立即执行一次,触发依赖收集 _effect.run(); // 返回 runner,允许手动触发 const runner = _effect.run.bind(_effect); (runner as any).effect = _effect; return runner; } // --- computed --- function computed<T>(getter: () => T) { let value: T; let dirty = true; // 脏标记:是否需要重新计算 const runner = effect(getter, { scheduler: () => { // getter 的依赖变化时,标记为脏但不立即计算 dirty = true; // 触发依赖 computed 值的 Effect 重新执行 trigger(computedObj, "value"); }, }); const computedObj = { get value() { if (dirty) { value = runner(); dirty = false; // 计算后清除脏标记 } track(computedObj, "value"); // computed 自身也可被依赖 return value; }, }; return computedObj; }

四、Vue3 响应式的 Trade-offs 分析

Proxy 的性能开销:Proxy 的 get/set 拦截比直接属性访问慢约 5-10 倍。对于高频访问的热路径(如循环中的数组元素),这个开销会累积。Vue3 通过缓存和惰性代理缓解了大部分问题,但极端场景下仍需注意。

深度响应式的内存占用:每个被代理的对象都会在 targetMap 中创建依赖记录。对于包含大量嵌套对象的数据结构,内存占用可能显著增加。如果某些嵌套对象不需要响应式,可以使用markRaw标记跳过代理。

Effect 的隐式依赖:依赖收集在 Effect 执行时自动发生,开发者无法直观看到"这个 Effect 依赖了哪些属性"。当 Effect 意外触发时,排查依赖关系需要借助开发工具。Vue DevTools 提供了依赖追踪功能,但在复杂场景下仍然不够直观。

computed 的缓存陷阱:computed 只在依赖变化时重新计算,但如果 getter 中有副作用(如发起 API 请求),副作用不会在缓存命中时执行。computed 应该是纯函数,副作用应该放在 watch 中。

五、总结

Vue3 响应式系统用 Proxy 替代 Object.defineProperty,解决了属性新增/删除检测和深度监听性能问题。核心机制是"Proxy 拦截 → 依赖收集 → 调度更新"三步链路。惰性深度代理确保只有被访问的属性才产生开销,批量调度避免同一 Effect 重复执行。落地时需要注意 Proxy 的性能开销、深度响应式的内存占用和 computed 的缓存语义。建议用shallowReactive处理大型对象,用markRaw跳过不需要响应式的部分。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/14 20:49:51

NSK超巨型重载滚珠丝杠技术详解

型号 HTF12016-7.5 属于 sources 中 NSK 专为大负载驱动开发的最核心、最顶级的 HTF 型&#xff08;大负载驱动用&#xff09;滚珠丝杠系列&#xff0c;采用成熟可靠且承受极端载荷能力极强的管循环式构造。 与您此前查询的 100 mm 轴径型号相比&#xff0c;该型号将丝杠轴外径…

作者头像 李华
网站建设 2026/6/14 20:43:54

AutoGPT实战:构建可验证的自主任务操作系统

1. 这不是“AI写稿工具”&#xff0c;而是一套正在成型的自主任务操作系统你有没有试过让一个AI帮你写周报&#xff0c;结果它真的写了——但顺手把你的OKR重新拆解成季度目标、把部门协作流程图重绘了一遍、连下周茶水间咖啡机采购比价表都列好了&#xff1f;这不是科幻片里的…

作者头像 李华
网站建设 2026/6/14 20:41:58

LabVIEW 工程化应用与场景落地指南

在工业 4.0 浪潮下&#xff0c;许多工程师都面临着同样的痛点&#xff1a;实验室里跑通的代码&#xff0c;一到产线就“水土不服”&#xff1b;多品牌设备协议各异&#xff0c;数据采集像是在解迷宫&#xff1b;更别提那些对时序要求极高的仪器控制&#xff0c;稍有延迟就会导致…

作者头像 李华
网站建设 2026/6/14 20:40:11

全网最全!2026AI论文网站榜单(覆盖 99% 毕业生论文需求)

本文精选13 款2026 年实测 AI 论文工具&#xff0c;按全流程全能型、垂直领域专精型、润色降重专家、文献管理助手四大类别排序&#xff0c;覆盖从选题到定稿全链路&#xff0c;适配本科 / 硕博 / 期刊全场景&#xff0c;附选型速查表与避坑指南&#xff0c;帮你快速找到最佳拍…

作者头像 李华
网站建设 2026/6/14 20:37:53

如何在3分钟内为Mac安装Windows驱动?Brigadier的自动化革命

如何在3分钟内为Mac安装Windows驱动&#xff1f;Brigadier的自动化革命 【免费下载链接】brigadier Fetch and install Boot Camp ESDs with ease. 项目地址: https://gitcode.com/gh_mirrors/bri/brigadier 在Mac上安装Windows系统时&#xff0c;最令人头疼的环节莫过于…

作者头像 李华