ES6 引入的 Proxy 提供了一种元编程能力——可以自定义对象的基本操作行为。通过 new Proxy(target, handler),你可以拦截并重新定义目标对象上的 13 种内部方法,覆盖了对象几乎一切可能的行为。下面逐一拆解这 13 种拦截操作,附上实用场景和简洁示例。
1. get(target, property, receiver)
拦截属性读取,包括普通属性、方法、原型链上的属性。
const user = { name: 'Alice' };
const proxy = new Proxy(user, {
get(target, prop) {
if (prop in target) {
return target[prop];
}
return '属性不存在';
}
});
console.log(proxy.name); // 'Alice'
console.log(proxy.age); // '属性不存在'
实用场景
- 默认值设置、属性不存在时的友好提示。
- 对象懒加载(如大型数据按需获取)。
- 访问日志记录(AOP)。
2. set(target, property, value, receiver)
拦截属性赋值。
const proxy = new Proxy({}, {
set(target, prop, value) {
if (typeof value === 'number') {
target[prop] = value;
return true; // 赋值成功
}
console.warn('只允许数值类型');
return false; // 拒绝赋值
}
});
proxy.age = 25; // 成功
proxy.name = 'A'; // 警告,赋值失败
实用场景
- 数据验证与类型检查。
- 双向绑定(如 Vue 的响应式原理底层)。
- 禁止修改只读对象。
3. has(target, property)
拦截 in 操作符。
const proxy = new Proxy({ secret: 1 }, {
has(target, prop) {
if (prop === 'secret') return false;
return prop in target;
}
});
console.log('secret' in proxy); // false
console.log('name' in proxy); // false
实用场景
- 隐藏某些私有属性。
- 扩展
in操作的行为(如数组“元素存在”判断)。
4. deleteProperty(target, property)
拦截 delete 操作。
const proxy = new Proxy({ id: 1 }, {
deleteProperty(target, prop) {
if (prop === 'id') {
console.log('id 属性不可删除');
return false;
}
delete target[prop];
return true;
}
});
delete proxy.id; // 被阻止
实用场景
- 保护关键属性不被删除。
- 资源清理(如自动断开引用、销毁关联对象)。
5. ownKeys(target)
拦截以下方法:Object.keys()、Object.getOwnPropertyNames()、Object.getOwnPropertySymbols(),以及 for...in(会触发 ownKeys 和 getOwnPropertyDescriptor)。
const proxy = new Proxy({ a: 1, b: 2 }, {
ownKeys(target) {
return ['a']; // 只暴露 a
}
});
console.log(Object.keys(proxy)); // ['a']
实用场景
- 限定可枚举属性,实现“白名单”视图。
- 性能优化(减少不必要的迭代)。
6. getOwnPropertyDescriptor(target, property)
拦截 Object.getOwnPropertyDescriptor()。
const proxy = new Proxy({}, {
getOwnPropertyDescriptor(target, prop) {
if (prop === 'x') {
return { value: 1, writable: false, enumerable: true, configurable: false };
}
return undefined;
}
});
实用场景
- 动态生成属性特性(如不可写、不可配置)。
- 与
Proxy配合创建“虚拟属性”。
7. defineProperty(target, property, descriptor)
拦截 Object.defineProperty() 和 Object.defineProperties()。
const proxy = new Proxy({}, {
defineProperty(target, prop, descriptor) {
if (prop.startsWith('_')) {
return false; // 不允许定义以下划线开头的属性
}
Object.defineProperty(target, prop, descriptor);
return true;
}
});
实用场景
- 限制属性创建规则。
- 同步更新外部状态(如在 Vue 中触发重新渲染)。
8. preventExtensions(target)
拦截 Object.preventExtensions()。
const proxy = new Proxy({}, {
preventExtensions(target) {
console.log('禁止扩展');
Object.preventExtensions(target);
return true;
}
});
实用场景
- 日志或权限检查,当对象被“封印”时发出通知。
9. isExtensible(target)
拦截 Object.isExtensible()。
const proxy = new Proxy({}, {
isExtensible(target) {
return false; // 伪装成不可扩展,但不影响实际扩展行为(可能导致不一致)
}
});
注意:此方法返回的值必须与实际目标状态一致,否则会抛出类型错误。通常不单独使用。
10. getPrototypeOf(target)
拦截 Object.getPrototypeOf()。
const proxy = new Proxy({}, {
getPrototypeOf(target) {
return Array.prototype; // 返回任意原型
}
});
实用场景
- 隐藏真实的原型链。
- 虚拟化对象原型(例如模拟继承关系)。
11. setPrototypeOf(target, prototype)
拦截 Object.setPrototypeOf()。
const proxy = new Proxy({}, {
setPrototypeOf(target, proto) {
// 禁止修改原型
return false;
}
});
实用场景
- 锁定原型,防止运行时篡改。
12. apply(target, thisArg, argumentsList)
拦截函数调用。该拦截器只对函数对象有效。
const double = n => n * 2;
const proxy = new Proxy(double, {
apply(target, thisArg, args) {
console.log(`调用参数: ${args}`);
return target(...args) * 2; // 结果加倍
}
});
console.log(proxy(5)); // 20(5*2*2)
实用场景
- 函数性能监控(记录调用次数、耗时)。
- 参数校验与转换。
- AOP 埋点(统一日志、异常处理)。
13. construct(target, argumentsList, newTarget)
拦截 new 操作,必须返回一个对象。
function Person(name) {
this.name = name;
}
const ProxyPerson = new Proxy(Person, {
construct(target, args) {
console.log(`使用参数 ${args} 构造`);
return new target(...args);
}
});
const p = new ProxyPerson('Alice'); // 触发拦截
实用场景
- 单例模式:确保
new只返回同一个实例。 - 构造函数参数预处理。
- 统计对象创建次数。
真实世界的应用:响应式系统雏形
Proxy 的 13 种拦截器不是孤立的,它们可以在 handler 中组合使用。最常见的组合就是构建响应式系统(如 Vue 3 的 reactive() 核心):
function reactive(obj) {
return new Proxy(obj, {
get(target, key) {
// 依赖收集……
return target[key];
},
set(target, key, val) {
target[key] = val;
// 触发更新……
return true;
}
});
}
借助 get 和 set 即可追踪数据读写,不再需要像 Object.defineProperty 那样递归劫持所有属性。而且 Proxy 还能直接监听数组索引变化、对象属性增删(deleteProperty),这是早期响应式实现无法做到的。
与 Object.defineProperty 的对比优势
| 方面 | Object.defineProperty | Proxy |
|------|-----------------------|---------|
| 拦截粒度 | 只能劫持现有属性 | 代理整个对象,监听新增/删除属性 |
| 数组监听 | 需要覆盖数组方法 | 直接监听索引和 length 的变化 |
| 性能 | 需递归为每个属性设置 getter/setter | 只需一层代理,懒处理 |
| 支持的拦截类型 | 仅 get/set | 13 种操作全覆盖 |
正是这些优势,使 Proxy 成为现代前端框架实现数据绑定、不可变数据、虚拟化对象的底层支柱。掌握这 13 种拦截操作,你就多了一把解锁高阶编程范式的钥匙。