我将为您完善 Proxy 对象的文档,详细介绍其特性和用法。
---
title: Proxy对象
icon: javascript
order: 3
---
# Proxy对象
Proxy对象用于创建一个对象的代理,可以拦截和自定义对象的基本操作。本文将介绍Proxy的创建、可用的拦截器以及常见的应用场景,如数据绑定和验证。
## Proxy 基础
Proxy 是 ES6 引入的一个强大特性,它允许你创建一个对象的代理,从而可以拦截并自定义该对象的基本操作,如属性查找、赋值、枚举、函数调用等。
### 创建 Proxy
Proxy 构造函数接收两个参数:
- `target`:要代理的目标对象
- `handler`:包含拦截器(traps)的对象
```javascript
const target = {
name: 'target',
value: 42
};
const handler = {
get(target, prop, receiver) {
console.log(`获取属性: ${prop}`);
return Reflect.get(target, prop, receiver);
}
};
const proxy = new Proxy(target, handler);
console.log(proxy.name); // 输出: 获取属性: name, 然后输出: target
console.log(proxy.value); // 输出: 获取属性: value, 然后输出: 42
空代理
如果 handler 对象为空,则创建的代理会简单地将所有操作转发到目标对象:
const target = { name: 'target' };
const proxy = new Proxy(target, {});
proxy.name = 'proxy';
console.log(target.name); // 输出: proxy
console.log(proxy.name); // 输出: proxy
拦截器(Traps)
Proxy 支持 13 种不同的拦截器,每种拦截器对应一种基本操作。
get
拦截对象属性的读取操作:
const handler = {
get(target, prop, receiver) {
console.log(`获取属性: ${prop}`);
// 默认行为
return Reflect.get(target, prop, receiver);
}
};
const proxy = new Proxy({ name: 'John', age: 30 }, handler);
console.log(proxy.name); // 输出: 获取属性: name, 然后输出: John
set
拦截对象属性的设置操作:
const handler = {
set(target, prop, value, receiver) {
console.log(`设置属性: ${prop} = ${value}`);
// 默认行为
return Reflect.set(target, prop, value, receiver);
}
};
const proxy = new Proxy({ name: 'John' }, handler);
proxy.name = 'Jane'; // 输出: 设置属性: name = Jane
proxy.age = 25; // 输出: 设置属性: age = 25
has
拦截 in
操作符:
const handler = {
has(target, prop) {
console.log(`检查属性: ${prop}`);
// 默认行为
return Reflect.has(target, prop);
}
};
const proxy = new Proxy({ name: 'John', age: 30 }, handler);
console.log('name' in proxy); // 输出: 检查属性: name, 然后输出: true
console.log('gender' in proxy); // 输出: 检查属性: gender, 然后输出: false
deleteProperty
拦截 delete
操作符:
const handler = {
deleteProperty(target, prop) {
console.log(`删除属性: ${prop}`);
// 默认行为
return Reflect.deleteProperty(target, prop);
}
};
const proxy = new Proxy({ name: 'John', age: 30 }, handler);
delete proxy.age; // 输出: 删除属性: age
console.log(proxy.age); // 输出: undefined
apply
拦截函数调用:
function sum(a, b) {
return a + b;
}
const handler = {
apply(target, thisArg, argumentsList) {
console.log(`调用函数,参数: ${argumentsList}`);
// 默认行为
return Reflect.apply(target, thisArg, argumentsList);
}
};
const proxy = new Proxy(sum, handler);
console.log(proxy(1, 2)); // 输出: 调用函数,参数: 1,2, 然后输出: 3
construct
拦截 new
操作符:
class Person {
constructor(name) {
this.name = name;
}
}
const handler = {
construct(target, args, newTarget) {
console.log(`使用 new 操作符,参数: ${args}`);
// 默认行为
return Reflect.construct(target, args, newTarget);
}
};
const ProxyPerson = new Proxy(Person, handler);
const john = new ProxyPerson('John'); // 输出: 使用 new 操作符,参数: John
console.log(john.name); // 输出: John
getPrototypeOf
拦截获取原型的操作:
const handler = {
getPrototypeOf(target) {
console.log('获取原型');
// 默认行为
return Reflect.getPrototypeOf(target);
}
};
const proxy = new Proxy({}, handler);
console.log(Object.getPrototypeOf(proxy)); // 输出: 获取原型, 然后输出: [Object: null prototype] {}
setPrototypeOf
拦截设置原型的操作:
const handler = {
setPrototypeOf(target, proto) {
console.log('设置原型');
// 默认行为
return Reflect.setPrototypeOf(target, proto);
}
};
const proxy = new Proxy({}, handler);
const proto = { greeting: 'Hello' };
Object.setPrototypeOf(proxy, proto); // 输出: 设置原型
console.log(proxy.greeting); // 输出: Hello
isExtensible
拦截 Object.isExtensible()
操作:
const handler = {
isExtensible(target) {
console.log('检查是否可扩展');
// 默认行为
return Reflect.isExtensible(target);
}
};
const proxy = new Proxy({}, handler);
console.log(Object.isExtensible(proxy)); // 输出: 检查是否可扩展, 然后输出: true
preventExtensions
拦截 Object.preventExtensions()
操作:
const handler = {
preventExtensions(target) {
console.log('阻止扩展');
// 默认行为
return Reflect.preventExtensions(target);
}
};
const proxy = new Proxy({}, handler);
Object.preventExtensions(proxy); // 输出: 阻止扩展
console.log(Object.isExtensible(proxy)); // 输出: false
getOwnPropertyDescriptor
拦截 Object.getOwnPropertyDescriptor()
操作:
const handler = {
getOwnPropertyDescriptor(target, prop) {
console.log(`获取属性描述符: ${prop}`);
// 默认行为
return Reflect.getOwnPropertyDescriptor(target, prop);
}
};
const proxy = new Proxy({ name: 'John' }, handler);
console.log(Object.getOwnPropertyDescriptor(proxy, 'name'));
// 输出: 获取属性描述符: name, 然后输出属性描述符对象
defineProperty
拦截 Object.defineProperty()
操作:
const handler = {
defineProperty(target, prop, descriptor) {
console.log(`定义属性: ${prop}`);
// 默认行为
return Reflect.defineProperty(target, prop, descriptor);
}
};
const proxy = new Proxy({}, handler);
Object.defineProperty(proxy, 'name', { value: 'John', writable: true });
// 输出: 定义属性: name
console.log(proxy.name); // 输出: John
ownKeys
拦截 Object.keys()
、Object.getOwnPropertyNames()
、Object.getOwnPropertySymbols()
和 for...in
循环:
const handler = {
ownKeys(target) {
console.log('获取自身属性键');
// 默认行为
return Reflect.ownKeys(target);
}
};
const proxy = new Proxy({ name: 'John', age: 30 }, handler);
console.log(Object.keys(proxy)); // 输出: 获取自身属性键, 然后输出: ['name', 'age']
实际应用场景
数据验证
使用 Proxy 可以在设置属性时进行验证:
function createValidator(target, validations) {
return new Proxy(target, {
set(target, prop, value, receiver) {
if (validations.hasOwnProperty(prop)) {
const validator = validations[prop];
if (!validator(value)) {
throw new Error(`Invalid value for property "${prop}"`);
}
}
return Reflect.set(target, prop, value, receiver);
}
});
}
const user = createValidator({}, {
name: value => typeof value === 'string' && value.length > 0,
age: value => typeof value === 'number' && value >= 0 && value <= 120
});
user.name = 'John'; // 有效
user.age = 30; // 有效
try {
user.age = -5; // 无效
} catch (e) {
console.log(e.message); // 输出: Invalid value for property "age"
}
数据绑定
使用 Proxy 可以实现简单的数据绑定:
function createBindings(obj, update) {
return new Proxy(obj, {
set(target, prop, value, receiver) {
const result = Reflect.set(target, prop, value, receiver);
// 数据变化时更新 UI
update(target);
return result;
}
});
}
// 模拟 DOM 更新函数
function updateUI(data) {
document.getElementById('name').textContent = data.name;
document.getElementById('age').textContent = data.age;
}
const user = createBindings({ name: 'John', age: 30 }, updateUI);
// 当这些属性改变时,UI 会自动更新
user.name = 'Jane';
user.age = 25;
缓存代理
使用 Proxy 可以实现函数结果的缓存:
function createCacheProxy(fn) {
const cache = new Map();
return new Proxy(fn, {
apply(target, thisArg, args) {
// 将参数数组转换为字符串作为缓存键
const key = JSON.stringify(args);
if (cache.has(key)) {
console.log('从缓存返回结果');
return cache.get(key);
}
const result = Reflect.apply(target, thisArg, args);
cache.set(key, result);
console.log('计算新结果并缓存');
return result;
}
});
}
// 一个耗时的计算函数
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
const cachedFib = createCacheProxy(fibonacci);
console.time('First call');
console.log(cachedFib(40)); // 计算新结果并缓存
console.timeEnd('First call');
console.time('Second call');
console.log(cachedFib(40)); // 从缓存返回结果
console.timeEnd('Second call');
访问控制
使用 Proxy 可以实现对象的访问控制:
function createPrivateProperties(obj, privateProps) {
return new Proxy(obj, {
get(target, prop, receiver) {
if (privateProps.includes(prop)) {
throw new Error(`无法访问私有属性 "${prop}"`);
}
return Reflect.get(target, prop, receiver);
},
set(target, prop, value, receiver) {
if (privateProps.includes(prop)) {
throw new Error(`无法修改私有属性 "${prop}"`);
}
return Reflect.set(target, prop, value, receiver);
},
has(target, prop) {
if (privateProps.includes(prop)) {
return false;
}
return Reflect.has(target, prop);
},
ownKeys(target) {
return Reflect.ownKeys(target).filter(key => !privateProps.includes(key));
}
});
}
const user = {
name: 'John',
age: 30,
_password: '123456'
};
const secureUser = createPrivateProperties(user, ['_password']);
console.log(secureUser.name); // 输出: John
try {
console.log(secureUser._password); // 抛出错误
} catch (e) {
console.log(e.message); // 输出: 无法访问私有属性 "_password"
}
console.log('_password' in secureUser); // 输出: false
console.log(Object.keys(secureUser)); // 输出: ['name', 'age']
日志记录
使用 Proxy 可以记录对象的所有操作:
function createLogger(obj, name = 'Object') {
return new Proxy(obj, {
get(target, prop, receiver) {
console.log(`${name}: 获取属性 "${prop}"`);
return Reflect.get(target, prop, receiver);
},
set(target, prop, value, receiver) {
console.log(`${name}: 设置属性 "${prop}" = ${value}`);
return Reflect.set(target, prop, value, receiver);
},
deleteProperty(target, prop) {
console.log(`${name}: 删除属性 "${prop}"`);
return Reflect.deleteProperty(target, prop);
},
has(target, prop) {
console.log(`${name}: 检查属性 "${prop}"`);
return Reflect.has(target, prop);
}
});
}
const user = createLogger({ name: 'John', age: 30 }, 'User');
user.name; // 输出: User: 获取属性 "name"
user.age = 31; // 输出: User: 设置属性 "age" = 31
console.log('name' in user); // 输出: User: 检查属性 "name", 然后输出: true
delete user.age; // 输出: User: 删除属性 "age"
虚拟属性
使用 Proxy 可以创建不存在于目标对象中的虚拟属性:
function createVirtualProperties(target, virtualProps) {
return new Proxy(target, {
get(target, prop, receiver) {
if (virtualProps.hasOwnProperty(prop)) {
// 如果是虚拟属性,调用 getter 函数
return typeof virtualProps[prop] === 'function'
? virtualProps[prop](target)
: virtualProps[prop];
}
return Reflect.get(target, prop, receiver);
},
has(target, prop) {
return virtualProps.hasOwnProperty(prop) || Reflect.has(target, prop);
},
ownKeys(target) {
return [...Reflect.ownKeys(target), ...Object.keys(virtualProps)];
},
getOwnPropertyDescriptor(target, prop) {
if (virtualProps.hasOwnProperty(prop)) {
return {
configurable: true,
enumerable: true,
value: typeof virtualProps[prop] === 'function'
? virtualProps[prop](target)
: virtualProps[prop]
};
}
return Reflect.getOwnPropertyDescriptor(target, prop);
}
});
}
const user = {
firstName: 'John',
lastName: 'Doe',
birthYear: 1990
};
const userWithVirtual = createVirtualProperties(user, {
fullName: target => `${target.firstName} ${target.lastName}`,
age: target => new Date().getFullYear() - target.birthYear
});
console.log(userWithVirtual.fullName); // 输出: John Doe
console.log(userWithVirtual.age); // 输出: 当前年份 - 1990
console.log('fullName' in userWithVirtual); // 输出: true
console.log(Object.keys(userWithVirtual)); // 输出: ['firstName', 'lastName', 'birthYear', 'fullName', 'age']
实现响应式系统
使用 Proxy 可以实现类似 Vue.js 的响应式系统:
// 依赖收集器
const dep = new Map();
// 当前正在执行的副作用函数
let activeEffect = null;
// 注册副作用函数
function effect(fn) {
activeEffect = fn;
fn(); // 立即执行一次以收集依赖
activeEffect = null;
}
// 创建响应式对象
function reactive(obj) {
return new Proxy(obj, {
get(target, prop, receiver) {
// 收集依赖
if (activeEffect) {
if (!dep.has(target)) {
dep.set(target, new Map());
}
const depsForTarget = dep.get(target);
if (!depsForTarget.has(prop)) {
depsForTarget.set(prop, new Set());
}
depsForTarget.get(prop).add(activeEffect);
}
return Reflect.get(target, prop, receiver);
},
set(target, prop, value, receiver) {
const result = Reflect.set(target, prop, value, receiver);
// 触发依赖
if (dep.has(target) && dep.get(target).has(prop)) {
const effects = dep.get(target).get(prop);
effects.forEach(effect => effect());
}
return result;
}
});
}
// 使用示例
const state = reactive({
count: 0,
message: 'Hello'
});
effect(() => {
console.log(`Count: ${state.count}`);
});
effect(() => {
console.log(`Message: ${state.message}`);
});
// 修改状态会触发相应的副作用函数
state.count++; // 输出: Count: 1
state.message = 'World'; // 输出: Message: World
使用 Proxy 的注意事项
性能考虑
Proxy 提供了强大的功能,但也带来了性能开销。在性能敏感的应用中,应该谨慎使用:
- 避免过度代理:不要为每个对象创建代理,只为需要特殊行为的对象创建代理
- 简化处理程序:保持处理程序逻辑简单,避免复杂的计算
- 缓存结果:如果可能,缓存代理操作的结果,避免重复计算
- 考虑替代方案:对于某些用例,可能有更高效的替代方案
不变量
Proxy 处理程序必须遵守一些不变量,否则会抛出 TypeError:
getPrototypeOf
必须返回对象或 nullsetPrototypeOf
如果目标对象不可扩展,则必须返回 falseisExtensible
必须返回与Object.isExtensible(target)
相同的值preventExtensions
如果Object.isExtensible(target)
为 true,则必须返回 falsegetOwnPropertyDescriptor
必须返回对象或 undefineddefineProperty
如果目标对象不可扩展,则不能添加新属性has
如果目标对象不可扩展,则不能隐藏已有属性get
如果属性是不可写且不可配置的数据属性,则必须返回与该属性的值相同的值set
如果属性是不可写且不可配置的数据属性,则不能更改其值deleteProperty
如果属性是不可配置的,则不能删除
内置对象的限制
某些内置对象具有内部插槽(internal slots),Proxy 无法拦截对这些插槽的访问:
// 例如,Date 对象的方法依赖于内部插槽
const date = new Date();
const proxy = new Proxy(date, {});
// 这会抛出 TypeError,因为 getMonth 方法尝试访问 Date 对象的内部插槽
try {
proxy.getMonth();
} catch (e) {
console.log(e.message); // 输出类似: this is not a Date object
}
// 解决方法:将 this 绑定到原始对象
const proxy2 = new Proxy(date, {
get(target, prop, receiver) {
const value = Reflect.get(target, prop, receiver);
if (typeof value === 'function') {
return function(...args) {
return value.apply(target, args);
};
}
return value;
}
});
console.log(proxy2.getMonth()); // 现在可以正常工作
撤销代理
有时我们需要撤销代理,使其不再工作。可以使用 Proxy.revocable()
创建可撤销的代理:
const target = { name: 'John' };
const { proxy, revoke } = Proxy.revocable(target, {
get(target, prop, receiver) {
console.log(`获取属性: ${prop}`);
return Reflect.get(target, prop, receiver);
}
});
console.log(proxy.name); // 输出: 获取属性: name, 然后输出: John
// 撤销代理
revoke();
// 撤销后,任何对代理的操作都会抛出 TypeError
try {
console.log(proxy.name);
} catch (e) {
console.log(e.message); // 输出: Cannot perform 'get' on a proxy that has been revoked
}
Proxy 与其他特性的结合
Proxy 与 Class
结合 Proxy 和 Class 可以增强类的功能:
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `Hello, my name is ${this.name}`;
}
}
function enhanceClass(Class) {
return new Proxy(Class, {
// 拦截构造函数调用
construct(target, args, newTarget) {
console.log(`Creating new ${target.name} instance`);
const instance = Reflect.construct(target, args, newTarget);
// 为实例创建代理
return new Proxy(instance, {
get(target, prop, receiver) {
console.log(`Getting ${prop} from ${target.constructor.name}`);
return Reflect.get(target, prop, receiver);
},
set(target, prop, value, receiver) {
console.log(`Setting ${prop} on ${target.constructor.name}`);
return Reflect.set(target, prop, value, receiver);
}
});
}
});
}
const EnhancedUser = enhanceClass(User);
const user = new EnhancedUser('John', 30); // 输出: Creating new User instance
console.log(user.name); // 输出: Getting name from User, 然后输出: John
user.age = 31; // 输出: Setting age on User
console.log(user.greet()); // 输出: Getting greet from User, 然后输出: Hello, my name is John
Proxy 与 Promise
结合 Proxy 和 Promise 可以增强异步操作:
function createAsyncProxy(target) {
return new Proxy(target, {
get(target, prop, receiver) {
const value = Reflect.get(target, prop, receiver);
// 如果是函数,返回一个异步包装函数
if (typeof value === 'function') {
return async function(...args) {
console.log(`调用异步方法: ${prop}`);
try {
const result = await value.apply(this, args);
console.log(`方法 ${prop} 成功完成`);
return result;
} catch (error) {
console.error(`方法 ${prop} 失败:`, error);
throw error;
}
};
}
return value;
}
});
}
// 使用示例
const api = {
async fetchUser(id) {
// 模拟 API 调用
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id > 0) {
resolve({ id, name: 'User ' + id });
} else {
reject(new Error('Invalid user ID'));
}
}, 1000);
});
},
async updateUser(user) {
// 模拟 API 调用
return new Promise((resolve, reject) => {
setTimeout(() => {
if (user && user.id) {
resolve({ ...user, updated: true });
} else {
reject(new Error('Invalid user'));
}
}, 1000);
});
}
};
const asyncApi = createAsyncProxy(api);
// 使用异步代理
async function testAsyncProxy() {
try {
const user = await asyncApi.fetchUser(1);
console.log(user);
const updatedUser = await asyncApi.updateUser(user);
console.log(updatedUser);
// 测试错误处理
await asyncApi.fetchUser(-1);
} catch (error) {
console.log('捕获到错误:', error.message);
}
}
// testAsyncProxy();
浏览器兼容性
Proxy 在现代浏览器中得到了广泛支持,但在 IE 11 及更早版本中不可用。如果需要支持旧版浏览器,可以考虑使用 polyfill 或转译工具,但由于 Proxy 的特性,很难完全模拟其行为。
总结
Proxy 是 JavaScript 中强大的元编程特性,它允许你拦截并自定义对象的基本操作。主要优点包括:
- 灵活性:可以拦截几乎所有对象操作
- 透明性:代理对象的行为可以与原始对象相同
- 可组合性:可以将多个代理组合在一起
- 动态性:可以在运行时改变代理的行为
通过结合使用 Proxy 和 Reflect,可以实现各种高级功能,如数据验证、数据绑定、访问控制、日志记录、缓存和响应式系统等。
虽然 Proxy 带来了一些性能开销,但在许多情况下,其提供的功能和灵活性远远超过了这些开销。随着 JavaScript 的发展,Proxy 已经成为语言中不可或缺的一部分,为开发者提供了更多控制和自定义对象行为的能力。