之前写了一篇介绍 Object.defineProperty的,提到proxy,二者有一些共性,也都是前端框架Vue的核心机制,所以再写一篇介绍一下proxy的基础原理和使用。
在 JavaScript 中,Proxy
是 ES6 引入的一个元编程特性,用于创建对象的代理,从而可以拦截并自定义对该对象的基本操作(如属性访问、赋值、枚举、函数调用等)。与 Object.defineProperty()
相比,Proxy
提供了更强大、更全面的元编程能力。
核心作用
- 拦截对象操作
通过定义拦截器(trap),可以捕获并自定义对象的各种操作(如属性读取、赋值、函数调用等)。 - 实现元编程
允许修改语言的底层行为,例如自定义属性访问权限、实现数据验证、模拟私有属性等。 - 创建响应式系统
比Object.defineProperty()
更适合实现响应式数据绑定(如 Vue.js 3.x),因为它能拦截更广泛的操作且支持深层监听。
基本语法
const proxy = new Proxy(target, handler);
target
:需要被代理的对象。handler
:一个对象,包含拦截各种操作的陷阱函数(trap)。
常用拦截器(Traps)
拦截器 | 触发时机 |
---|---|
get(target, prop, receiver) | 读取属性时触发 |
set(target, prop, value, receiver) | 设置属性值时触发 |
has(target, prop) | 判断属性是否存在(in 操作符) |
deleteProperty(target, prop) | 删除属性时触发 |
ownKeys(target) | 获取对象所有属性键(如 Object.keys() ) |
apply(target, thisArg, args) | 代理函数调用时触发 |
construct(target, args, newTarget) | 代理构造函数调用时触发(new 操作符) |
用法示例
1. 基本拦截:属性读取和赋值
const person = { name: 'John', age: 30 };const proxy = new Proxy(person, {// 拦截属性读取get(target, prop) {console.log(`读取属性 ${prop}`);return target[prop];},// 拦截属性赋值set(target, prop, value) {console.log(`设置属性 ${prop} 为 ${value}`);if (prop === 'age' && typeof value !== 'number') {throw new Error('年龄必须是数字');}target[prop] = value;return true; // 表示赋值成功}
});console.log(proxy.name); // 读取属性 name → John
proxy.age = 31; // 设置属性 age 为 31
proxy.age = 'thirty'; // Error: 年龄必须是数字
2. 实现数据验证
const validator = {set(target, prop, value) {if (prop === 'age' && value < 0) {throw new Error('年龄不能为负数');}target[prop] = value;return true;}
};const person = new Proxy({ age: 30 }, validator);
person.age = -5; // Error: 年龄不能为负数
3. 实现私有属性
const privateData = new WeakMap();const createPerson = (name, age) => {privateData.set({ name, age }, { salary: 5000 });return new Proxy({ name, age }, {get(target, prop) {if (privateData.has(target) && prop === 'salary') {return privateData.get(target)[prop];}return target[prop];}});
};const person = createPerson('John', 30);
console.log(person.name); // John
console.log(person.salary); // undefined(外部无法访问)
4. 函数调用拦截
const sum = (a, b) => a + b;const proxy = new Proxy(sum, {apply(target, thisArg, args) {console.log(`调用函数,参数:${args}`);return target(...args) * 2; // 结果翻倍}
});console.log(proxy(2, 3)); // 调用函数,参数:2,3 → 10
与 Object.defineProperty()
的对比
特性 | Object.defineProperty() | Proxy |
---|---|---|
监听范围 | 只能监听对象的已有属性(需逐个定义) | 可以监听整个对象(包括新增属性) |
深层监听 | 需要递归处理嵌套对象 | 可以通过递归代理实现深层监听 |
数组支持 | 对数组的监听有限(需特殊处理) | 全面支持数组操作(如 push 、pop ) |
元编程能力 | 仅能控制单个属性的行为 | 可以拦截多种操作(如 in 、delete 、函数调用等) |
性能 | 对于大量属性的对象,性能略高 | 对于频繁操作的场景,性能略低 |
兼容性 | ES5(支持 IE9+) | ES6(不支持 IE) |
应用场景
-
响应式系统
Vue.js 3.x 使用Proxy
替代Object.defineProperty()
实现响应式数据:const reactive = (target) => {return new Proxy(target, {get(target, prop) {// 依赖收集return target[prop];},set(target, prop, value) {target[prop] = value;// 触发更新updateDOM();return true;}}); };
-
数据验证与转换
在属性赋值时自动验证或转换数据:const withValidation = (target, validators) => {return new Proxy(target, {set(target, prop, value) {if (validators[prop] && !validators[prop](value)) {throw new Error(`Invalid value for ${prop}`);}target[prop] = value;return true;}}); };const person = withValidation({ name: '', age: 0 },{ age: (v) => typeof v === 'number' && v > 0 } );
-
日志记录与性能监控
拦截对象操作并记录日志:const withLogging = (target) => {return new Proxy(target, {get(target, prop) {console.log(`Getting ${prop}`);return target[prop];},set(target, prop, value) {console.log(`Setting ${prop} to ${value}`);target[prop] = value;return true;}}); };
注意事项
- 兼容性:
Proxy
是 ES6 特性,不支持 IE。 - 性能开销:
Proxy
的拦截操作比直接访问对象属性有更高的性能开销,不适合高性能场景。 - this 指向:在
Proxy
的陷阱函数中,this
指向handler
对象,而非target
对象。 - 反射 API:通常与
Reflect
对象配合使用,以保持原生行为:const proxy = new Proxy(target, {get(target, prop, receiver) {return Reflect.get(target, prop, receiver);} });
总结
Object.defineProperty()
:适合简单场景(如单个对象的属性监听),兼容性好,但功能有限。Proxy
:适合复杂场景(如框架开发、全面的数据拦截),功能强大,但有兼容性和性能限制。
选择哪种方式取决于具体需求:若需要全面控制对象操作且不考虑 IE 兼容性,Proxy
是更好的选择;若需要兼容旧浏览器或仅需简单属性监听,可使用 Object.defineProperty()
。