Proxy:拦截对象的十三种行为
"Proxy 是 JavaScript 中最强大的元编程特性之一,它允许我们拦截并自定义对象的几乎所有操作。通过 Proxy,我们可以实现从数据绑定到访问控制等各种高级功能。"
Proxy 是 ES6 引入的一个内建对象,它允许我们创建一个代理对象,用于拦截和自定义 JavaScript 对象的基本操作(如属性查找、赋值、枚举、函数调用等)。这为 JavaScript 提供了强大的元编程能力。
一、Proxy 基础语法
Proxy 构造函数接受两个参数:target(目标对象)和 handler(处理器对象):
javascript
const target = {};
const handler = {
// 拦截器方法
};
const proxy = new Proxy(target, handler);Proxy 创建一个代理对象,该对象会拦截应用到目标对象上的操作,并根据处理器对象中的陷阱(traps)方法来定义这些操作的自定义行为。
二、Proxy 的十三种陷阱方法
Proxy 支持拦截对象的十三种基本操作:
1. get - 属性读取陷阱
javascript
const target = { message: 'Hello' };
const handler = {
get(target, prop, receiver) {
console.log(`读取属性: ${prop}`);
return target[prop];
}
};
const proxy = new Proxy(target, handler);
console.log(proxy.message); // 读取属性: message → "Hello"2. set - 属性设置陷阱
javascript
const target = {};
const handler = {
set(target, prop, value, receiver) {
console.log(`设置属性 ${prop} 为 ${value}`);
target[prop] = value;
return true; // 表示设置成功
}
};
const proxy = new Proxy(target, handler);
proxy.name = 'JavaScript'; // 设置属性 name 为 JavaScript3. has - in 操作符陷阱
javascript
const target = { public: true };
const handler = {
has(target, prop) {
// 隐藏以 _ 开头的私有属性
if (prop.startsWith('_')) {
return false;
}
return prop in target;
}
};
const proxy = new Proxy(target, handler);
console.log('public' in proxy); // true
console.log('_private' in proxy); // false4. deleteProperty - delete 操作符陷阱
javascript
const target = { canDelete: true, cannotDelete: true };
const handler = {
deleteProperty(target, prop) {
if (prop === 'cannotDelete') {
console.log(`不能删除属性: ${prop}`);
return false; // 阻止删除
}
return delete target[prop];
}
};
const proxy = new Proxy(target, handler);
delete proxy.canDelete; // 成功删除
delete proxy.cannotDelete; // 不能删除属性: cannotDelete5. ownKeys - Object.getOwnPropertyNames 和 Object.keys 陷阱
javascript
const target = { visible: 1, _hidden: 2 };
const handler = {
ownKeys(target) {
// 只返回非私有属性
return Reflect.ownKeys(target).filter(key => !key.startsWith('_'));
}
};
const proxy = new Proxy(target, handler);
console.log(Object.keys(proxy)); // ["visible"]6. getOwnPropertyDescriptor - Object.getOwnPropertyDescriptor 陷阱
javascript
const target = { normal: 1 };
const handler = {
getOwnPropertyDescriptor(target, prop) {
console.log(`获取属性描述符: ${prop}`);
return Object.getOwnPropertyDescriptor(target, prop);
}
};
const proxy = new Proxy(target, handler);
Object.getOwnPropertyDescriptor(proxy, 'normal');
// 获取属性描述符: normal7. defineProperty - Object.defineProperty 陷阱
javascript
const target = {};
const handler = {
defineProperty(target, prop, descriptor) {
console.log(`定义属性: ${prop}`);
return Object.defineProperty(target, prop, descriptor);
}
};
const proxy = new Proxy(target, handler);
Object.defineProperty(proxy, 'newProp', {
value: 'newValue',
writable: true
});
// 定义属性: newProp8. apply - 函数调用陷阱
javascript
const target = function() { return 'original'; };
const handler = {
apply(target, thisArg, argumentsList) {
console.log('函数被调用');
return 'proxied: ' + target.apply(thisArg, argumentsList);
}
};
const proxy = new Proxy(target, handler);
console.log(proxy()); // 函数被调用 → "proxied: original"9. construct - new 操作符陷阱
javascript
class OriginalClass {
constructor(value) {
this.value = value;
}
}
const handler = {
construct(target, argumentsList, newTarget) {
console.log('构造函数被调用');
const instance = Reflect.construct(target, argumentsList, newTarget);
instance.proxied = true;
return instance;
}
};
const ProxyClass = new Proxy(OriginalClass, handler);
const instance = new ProxyClass(42);
console.log(instance.value); // 42
console.log(instance.proxied); // true10. getPrototypeOf - Object.getPrototypeOf 陷阱
javascript
const target = {};
const handler = {
getPrototypeOf(target) {
console.log('获取原型');
return Object.getPrototypeOf(target);
}
};
const proxy = new Proxy(target, handler);
Object.getPrototypeOf(proxy); // 获取原型11. setPrototypeOf - Object.setPrototypeOf 陷阱
javascript
const target = {};
const proto = { x: 1 };
const handler = {
setPrototypeOf(target, prototype) {
console.log('设置原型');
return Object.setPrototypeOf(target, prototype);
}
};
const proxy = new Proxy(target, handler);
Object.setPrototypeOf(proxy, proto); // 设置原型12. isExtensible - Object.isExtensible 陷阱
javascript
const target = {};
const handler = {
isExtensible(target) {
console.log('检查是否可扩展');
return Object.isExtensible(target);
}
};
const proxy = new Proxy(target, handler);
Object.isExtensible(proxy); // 检查是否可扩展13. preventExtensions - Object.preventExtensions 陷阱
javascript
const target = {};
const handler = {
preventExtensions(target) {
console.log('阻止扩展');
return Object.preventExtensions(target);
}
};
const proxy = new Proxy(target, handler);
Object.preventExtensions(proxy); // 阻止扩展三、实际应用示例
1. 实现负索引数组
javascript
function createNegativeArray(...elements) {
const target = [...elements];
const handler = {
get(target, prop) {
// 处理负索引
if (typeof prop === 'string' && /^-\d+$/.test(prop)) {
const index = +prop;
const actualIndex = target.length + index;
if (actualIndex >= 0 && actualIndex < target.length) {
return target[actualIndex];
}
}
return target[prop];
},
set(target, prop, value) {
// 处理负索引设置
if (typeof prop === 'string' && /^-\d+$/.test(prop)) {
const index = +prop;
const actualIndex = target.length + index;
if (actualIndex >= 0 && actualIndex < target.length) {
target[actualIndex] = value;
return true;
}
}
target[prop] = value;
return true;
}
};
return new Proxy(target, handler);
}
const arr = createNegativeArray('a', 'b', 'c', 'd');
console.log(arr[-1]); // 'd'
console.log(arr[-2]); // 'c'
arr[-1] = 'z';
console.log(arr[3]); // 'z'2. 实现链式调用
javascript
function createChainable(obj) {
const handler = {
get(target, prop) {
if (prop in target) {
return target[prop];
}
// 创建链式调用方法
return function(...args) {
console.log(`调用方法: ${prop}`, args);
// 这里可以添加实际的逻辑
return new Proxy({}, handler); // 返回新的代理以支持链式调用
};
}
};
return new Proxy(obj, handler);
}
const api = createChainable({});
api.users().list().filter({ active: true }).sort('name');
// 调用方法: users []
// 调用方法: list []
// 调用方法: filter [ { active: true } ]
// 调用方法: sort [ 'name' ]3. 实现自动深响应
javascript
function createReactive(obj) {
// 递归创建响应式对象
function makeReactive(target) {
if (typeof target !== 'object' || target === null) {
return target;
}
const handler = {
get(target, prop) {
console.log(`获取属性: ${prop}`);
const value = target[prop];
// 递归处理嵌套对象
return makeReactive(value);
},
set(target, prop, value) {
console.log(`设置属性: ${prop} = ${value}`);
target[prop] = value;
// 触发更新
triggerUpdate(prop, value);
return true;
}
};
return new Proxy(target, handler);
}
function triggerUpdate(prop, value) {
console.log(`触发更新: ${prop} 已更新为 ${value}`);
// 这里可以实现实际的更新逻辑
}
return makeReactive(obj);
}
const reactiveObj = createReactive({ user: { name: 'Alice', age: 30 } });
console.log(reactiveObj.user.name); // 获取属性: user → 获取属性: name → "Alice"
reactiveObj.user.age = 31; // 设置属性: age = 31 → 触发更新: age 已更新为 31四、注意事项和最佳实践
1. 性能考虑
Proxy 会带来一定的性能开销,特别是在频繁访问的场景中:
javascript
// 避免在高频操作中使用复杂的 Proxy
const expensiveProxy = new Proxy(target, {
get(target, prop) {
// 复杂的处理逻辑会显著影响性能
return target[prop];
}
});2. 正确返回布尔值
某些陷阱方法需要返回布尔值来表示操作是否成功:
javascript
const handler = {
set(target, prop, value) {
target[prop] = value;
return true; // 必须返回 true 表示设置成功
},
deleteProperty(target, prop) {
const result = delete target[prop];
return result; // 返回删除操作的结果
}
};3. 使用 Reflect 进行转发
在自定义陷阱方法中,通常使用 Reflect 来转发操作:
javascript
const handler = {
get(target, prop, receiver) {
// 添加自定义逻辑
console.log(`访问属性: ${prop}`);
// 转发给原始对象
return Reflect.get(target, prop, receiver);
}
};一句话总结
Proxy 提供了拦截和自定义 JavaScript 对象操作的强大能力,通过十三种陷阱方法,我们可以完全控制对象的行为,实现从数据绑定到访问控制等各种高级功能。
Proxy 是现代 JavaScript 中实现元编程的重要工具,它使我们能够创建具有自定义行为的对象,但同时也需要注意性能影响和正确使用各种陷阱方法。