一、Proxy是什么?以及核心概念
核心概念
Proxy 是 JavaScript 提供的一种元编程工具,可以作为对象的“代理”。它通过捕获对象的操作来实现拦截、改写或通知外部逻辑,从而对原对象进行间接控制。
在实现上,代理对象包裹了目标对象,所有对目标对象的操作都会触发对应的陷阱(traps),如 get、set、has、deleteProperty 等。
下面给出一个简单的示例,展示如何通过代理在读取属性时进行日志记录。
const target = { a: 1, b: 2 };
const handler = {get(target, prop, receiver) {console.log('访问属性:', prop);return Reflect.get(target, prop, receiver);}
};
const proxy = new Proxy(target, handler);
console.log(proxy.a); // 访问日志+返回 1
通过以上示例可以看到,get陷阱实现了读取拦截,而 Reflect 提供了透明的默认行为,避免偏离原对象的行为。
二、工作原理:从陷阱到 Reflect
陷阱机制与 Reflect 的角色
Proxy 的工作原理是:目标对象上的操作会被替换为对代理对象的陷阱调用,开发者在陷阱中决定如何处理、是否继续传播到目标对象。
常见的陷阱包括 get、set、has、ownKeys、deleteProperty 等。Reflect 提供与原生操作一致的默认实现,在陷阱中调用 Reflect 可以保持行为的一致性与可预测性。
const target = { x: 10, y: 20 };
const handler = {get(target, prop, receiver) {if (prop === 'x') {return Reflect.get(target, 'x', receiver) * 2; // 自定义逻辑}return Reflect.get(target, prop, receiver);}
};
const proxy = new Proxy(target, handler);
console.log(proxy.x); // 20
console.log(target.x); // 10 仍然保持原对象未改变
以上示例展示了陷阱如何改变行为并借助 Reflect 维持一致性。记住,对同一目标对象的直接访问与通过代理访问可能产生不同的行为,这也是 Proxy 的力量所在。
三、常见用法:拦截属性访问与修改
读取与写入拦截的实际案例
通过 get 与 set 陷阱,可以实现读取日志、数据校验、只读代理等多种场景。
其中,拦截对象的属性访问与修改的能力使得可以在不改变原对象结构的前提下,注入自定义行为,例如日志、校验、数据格式化等。
const target = { name: 'Alice', age: 30 };
const handler = {get(target, prop) {return Reflect.get(target, prop);},set(target, prop, value) {throw new Error(`不能修改属性 ${prop}`);}
};
const proxy = new Proxy(target, handler);
console.log(proxy.name); // Alice
proxy.age = 31; // 抛出错误:不能修改属性 age
此外,使用 has、ownKeys 等陷阱可以定制对象的可枚举性、键盘查询等行为,从而实现安全访问控制或数据隐藏。
另一个示例演示如何实现属性访问时的自定义校验。请看下方代码段。
const target = { port: 8080 };
const handler = {set(target, prop, value) {if (prop === 'port' && value > 65535) {throw new RangeError('端口号越界');}return Reflect.set(target, prop, value);}
};
const proxy = new Proxy(target, handler);
proxy.port = 3000; // 成功
proxy.port = 70000; // 抛出异常
总结来说,读取拦截与写入拦截的组合可以实现高度灵活的数据访问策略,同时保留原对象的结构与行为准则。

四、实操案例:结合日志与验证的代理
代理在日志与数据校验中的应用
利用 Proxy 可以在前端交互中对数据变化进行即时日志记录,便于调试和用户行为分析。日志代理会在每次读取与赋值时记录日志,而不会改变原有数据的存储。
下面的代码演示一个日志代理:读取时输出操作信息,写入时记录新值并更新目标对象。
const target = { count: 0, user: { name: 'Guest' } };
const handler = {get(target, prop, receiver) {console.log(`读取属性 ${String(prop)}`);return Reflect.get(target, prop, receiver);},set(target, prop, value, receiver) {console.log(`修改属性 ${String(prop)},新值:${value}`);return Reflect.set(target, prop, value, receiver);}
};
const proxy = new Proxy(target, handler);proxy.count = 1;
console.log(proxy.count);
proxy.user.name = 'Bob';
注意,对象深层嵌套也可以通过代理对其子对象进行拦截,但需要在目标对象的每一级结构上都应用代理,或者在 get 钩子中返回代理的子对象。


