1.4 代理与反射Proxy代理与 Reflect反射是 ES6 引入的元编程核心能力二者配套使用为 JavaScript 提供了拦截、自定义对象基础操作的能力。Proxy 负责架设操作拦截层Reflect 提供标准化的对象操作方法二者共同构成了现代前端响应式系统、数据校验、权限控制的底层实现基础也是理解主流框架底层原理的关键知识点。1.4.1 Proxy 代理对象Proxy 可以在目标对象外层创建一个代理对象外部对目标对象的所有操作属性读写、删除、遍历、函数调用等都会先经过代理层的拦截开发者可以在拦截逻辑中自定义行为实现对对象操作的管控与扩展。一、基础语法const proxy new Proxy(target, handler);target被代理的目标对象可以是普通对象、数组、函数、类等所有引用类型handler拦截器配置对象通过定义不同的陷阱trap方法拦截对应操作返回值代理对象外观与原对象一致操作会被 handler 拦截二、常用拦截方法陷阱Proxy 支持 13 种拦截操作覆盖对象的所有基础行为前端交互开发中高频使用的有以下 6 种拦截方法触发时机典型用途get(target, prop, receiver)读取代理对象属性时数据读取拦截、默认值补全、属性代理set(target, prop, value, receiver)设置代理对象属性时数据校验、变更监听、状态同步has(target, prop)in运算符判断属性时隐藏内部属性、存在性拦截deleteProperty(target, prop)delete删除属性时删除权限控制、删除行为监听apply(target, thisArg, args)代理函数被调用时函数参数校验、结果包装、埋点统计construct(target, args)new实例化代理类时实例化拦截、统一初始化逻辑基础示例属性读写拦截const user { name: 张三, age: 25 }; const userProxy new Proxy(user, { // 拦截读取操作 get(target, prop) { console.log(读取了属性${prop}); // 属性不存在时返回默认值 return prop in target ? target[prop] : 未知属性; }, // 拦截赋值操作 set(target, prop, value) { console.log(设置属性${prop} ${value}); // 年龄字段做数值校验 if (prop age typeof value ! number) { throw new TypeError(年龄必须是数字); } target[prop] value; return true; // set 拦截必须返回布尔值表示操作是否成功 } }); console.log(userProxy.name); // 打印读取了属性name → 张三 console.log(userProxy.gender); // 打印读取了属性gender → 未知属性 userProxy.age 26; // 打印设置属性age 26三、可撤销代理通过Proxy.revocable()可以创建可撤销的代理对象调用撤销方法后代理将完全失效所有操作都会抛出错误适合临时授权、生命周期管控场景。const { proxy, revoke } Proxy.revocable({ a: 1 }, {}); console.log(proxy.a); // 1 revoke(); // 撤销代理 console.log(proxy.a); // 抛出错误Cannot perform get on a proxy that has been revoked四、核心特性全对象代理代理的是整个对象而非单个属性可自动拦截新增属性无需提前遍历劫持全类型支持支持代理数组、Map、Set、函数、类等所有引用类型引用隔离代理对象与原对象是不同的引用直接修改原对象不会触发拦截透明代理未配置拦截的操作会直接透传到原对象行为与原对象完全一致1.4.2 Reflect 反射 APIReflect 是一个内置的静态对象提供了一系列操作对象的标准化函数方法。它将原本散落在Object上的命令式操作、运算符行为统一为函数式调用且所有方法与 Proxy 的拦截方法一一对应是 Proxy 拦截逻辑的最佳配套实现。一、核心设计价值统一对象操作 API将in、delete、new等运算符行为转为函数调用风格统一更易编程化处理合理的返回值操作结果返回布尔值表示成功 / 失败替代传统方法的报错行为更易做异常处理完美配套 Proxy与 Proxy 陷阱方法一一对应可在拦截器中通过 Reflect 执行对象的默认行为简化代码修正 this 指向支持传入receiver参数正确处理 getter/setter 中的 this 指向问题二、常用方法与对应关系Reflect 的方法与 Proxy 拦截器完全一一对应前端高频使用的方法如下Reflect方法等价传统操作差异说明Reflect.get(target, prop, receiver)target[prop]可指定 receiver 修正 this 指向Reflect.set(target, prop, value, receiver)target[prop] value返回布尔值表示操作是否成功Reflect.has(target, prop)prop in target函数式调用语义更清晰Reflect.deleteProperty(target, prop)delete target[prop]返回布尔值表示操作是否成功Reflect.ownKeys(target)Object.getOwnPropertyNamesObject.getOwnPropertySymbols返回所有自有属性键Reflect.construct(target, args)new target(...args)函数式实例化支持指定原型最佳实践Proxy Reflect 配套使用在 Proxy 拦截器中通过 Reflect 执行默认操作保证原生行为不被破坏同时正确处理 this 指向与返回值规范。const data { count: 0 }; const proxy new Proxy(data, { get(target, prop, receiver) { console.log(触发读取, prop); // 用 Reflect 执行默认读取逻辑保证 receiver 正确传递 return Reflect.get(target, prop, receiver); }, set(target, prop, value, receiver) { console.log(触发设置, prop, value); // 直接返回 Reflect.set 的布尔结果符合 set 拦截的返回值要求 return Reflect.set(target, prop, value, receiver); } }); proxy.count; // 打印触发读取count → 触发设置count 1三、与 Object 同名方法的差异以defineProperty为例Object.defineProperty操作失败时会抛出错误需要 try/catch 捕获Reflect.defineProperty操作失败时返回false可通过条件判断处理代码更优雅const obj {}; Object.freeze(obj); // 传统写法需捕获异常 try { Object.defineProperty(obj, a, { value: 1 }); } catch (e) { console.log(操作失败); } // Reflect 写法通过返回值判断 const success Reflect.defineProperty(obj, a, { value: 1 }); if (!success) console.log(操作失败);1.4.3 响应式基础原理响应式是现代前端框架的核心特性当数据发生变化时依赖该数据的视图、副作用函数会自动更新执行。Vue 3、MobX 等主流框架的响应式系统底层均基于 Proxy Reflect 实现。一、响应式核心三要素数据代理通过 Proxy 代理数据对象拦截属性的读写操作依赖收集在读取属性get 拦截时记录当前正在执行的副作用函数与该属性的关联关系依赖触发在修改属性set 拦截时找到该属性关联的所有副作用函数依次重新执行二、极简响应式实现以下是基于 Proxy 的最简响应式 Demo可直观理解核心原理// 存储当前正在执行的副作用函数 let activeEffect null; // 依赖收集桶target → prop → effect 集合 const targetMap new WeakMap(); // 1. 依赖收集读取属性时调用 function track(target, prop) { if (!activeEffect) return; let depsMap targetMap.get(target); if (!depsMap) targetMap.set(target, (depsMap new Map())); let deps depsMap.get(prop); if (!deps) depsMap.set(prop, (deps new Set())); deps.add(activeEffect); } // 2. 依赖触发修改属性时调用 function trigger(target, prop) { const depsMap targetMap.get(target); if (!depsMap) return; const deps depsMap.get(prop); deps deps.forEach(effect effect()); } // 3. 创建响应式对象 function reactive(target) { return new Proxy(target, { get(target, prop, receiver) { track(target, prop); return Reflect.get(target, prop, receiver); }, set(target, prop, value, receiver) { const result Reflect.set(target, prop, value, receiver); trigger(target, prop); return result; } }); } // 4. 注册副作用函数 function effect(fn) { activeEffect fn; fn(); // 首次执行触发依赖收集 activeEffect null; } // 测试 const state reactive({ count: 0, name: 测试 }); // 注册副作用数据变化时自动重新执行 effect(() { console.log(count 更新了, state.count); }); state.count; // 自动打印count 更新了1 state.count 10; // 自动打印count 更新了10三、Proxy 响应式的优势对比 Vue 2 使用的Object.defineProperty基于 Proxy 的实现有本质提升支持全量操作监听可监听属性新增、删除、数组下标修改、长度变化无需额外 hack支持集合类型可代理 Map、Set、WeakMap、WeakSet覆盖更多数据场景性能更优代理整个对象无需初始化时遍历所有属性递归劫持能力更全面可拦截函数调用、实例化等更多操作扩展能力更强