Proxy是ES6引入的一个强大功能,允许你拦截并自定义对象的基本操作(如属性查找、赋值、枚举、函数调用等)。
// 基本语法
const proxy = new Proxy(target, handler);
// target: 被代理的原始对象
// handler: 定义拦截操作的对象基础用法示例
1. 属性访问拦截
const person = {
name: 'John',
age: 30
};
const personProxy = new Proxy(person, {
// 拦截属性读取
get(target, property, receiver) {
console.log(`访问属性: ${property}`);
if (property in target) {
return target[property];
} else {
return `属性 ${property} 不存在`;
}
},
// 拦截属性设置
set(target, property, value, receiver) {
console.log(`设置属性: ${property} = ${value}`);
// 类型验证
if (property === 'age' && typeof value !== 'number') {
throw new Error('age必须是数字');
}
target[property] = value;
return true; // 表示设置成功
}
});
// 使用代理对象
console.log(personProxy.name); // 访问属性: name -> "John"
console.log(personProxy.height); // 访问属性: height -> "属性 height 不存在"
personProxy.age = 25; // 设置属性: age = 25
// personProxy.age = '25'; // 抛出错误: age必须是数字2. 函数调用拦截
function greet(name) {
return `Hello, ${name}!`;
}
const greetProxy = new Proxy(greet, {
// 拦截函数调用
apply(target, thisArg, argumentsList) {
console.log(`调用函数,参数:`, argumentsList);
// 参数验证
if (argumentsList.length === 0) {
return target.call(thisArg, 'World');
}
// 参数转换
const processedArgs = argumentsList.map(arg =>
typeof arg === 'string' ? arg.trim().toLowerCase() : arg
);
return target.apply(thisArg, processedArgs);
}
});
console.log(greetProxy('Alice')); // 调用函数,参数: ['Alice'] -> "Hello, alice!"
console.log(greetProxy()); // 调用函数,参数: [] -> "Hello, World!"Proxy Handler方法详解
1. 属性相关的拦截器
const obj = { x: 1, y: 2 };
const proxy = new Proxy(obj, {
// 拦截属性读取
get(target, property, receiver) {
console.log(`get: ${property}`);
return target[property];
},
// 拦截属性设置
set(target, property, value, receiver) {
console.log(`set: ${property} = ${value}`);
target[property] = value;
return true;
},
// 拦截 in 操作符
has(target, property) {
console.log(`has: ${property}`);
return property in target;
},
// 拦截 delete 操作
deleteProperty(target, property) {
console.log(`delete: ${property}`);
delete target[property];
return true;
},
// 拦截 Object.keys()、for...in 等
ownKeys(target) {
console.log('ownKeys called');
return Object.keys(target);
},
// 拦截 Object.getOwnPropertyDescriptor()
getOwnPropertyDescriptor(target, property) {
console.log(`getOwnPropertyDescriptor: ${property}`);
return Object.getOwnPropertyDescriptor(target, property);
}
});
// 测试各种操作
proxy.x; // get: x
proxy.z = 3; // set: z = 3
'x' in proxy; // has: x
delete proxy.y; // delete: y
Object.keys(proxy); // ownKeys called2. 函数相关的拦截器
function Calculator() {
this.result = 0;
}
Calculator.prototype.add = function(n) {
this.result += n;
return this;
};
const CalculatorProxy = new Proxy(Calculator, {
// 拦截 new 操作符
construct(target, argumentsList, newTarget) {
console.log('创建新实例');
const instance = new target(...argumentsList);
// 为实例添加额外功能
instance.history = [];
return instance;
},
// 拦截函数调用
apply(target, thisArg, argumentsList) {
console.log('直接调用构造函数');
return target.apply(thisArg, argumentsList);
}
});
const calc = new CalculatorProxy(); // 创建新实例
console.log(calc.history); // []实际应用场景
1. 数据验证和转换
class User {
constructor() {
return new Proxy(this, {
set(target, property, value) {
// 数据验证
switch (property) {
case 'email':
if (!/\S+@\S+\.\S+/.test(value)) {
throw new Error('无效的邮箱格式');
}
break;
case 'age':
if (value < 0 || value > 150) {
throw new Error('年龄必须在0-150之间');
}
break;
}
// 数据转换
if (property === 'name') {
value = value.trim().toLowerCase();
}
target[property] = value;
return true;
}
});
}
}
const user = new User();
user.name = ' ALICE '; // 自动转换为 'alice'
user.email = 'alice@test.com'; // 验证通过
user.age = 25; // 验证通过
// user.age = -5; // 抛出错误2. 属性的默认值和计算属性
const smartObject = new Proxy({}, {
get(target, property) {
// 如果属性不存在,返回默认值
if (!(property in target)) {
// 计算属性
if (property === 'fullName') {
return `${target.firstName || ''} ${target.lastName || ''}`.trim();
}
// 默认值
if (property === 'status') {
return 'active';
}
return undefined;
}
return target[property];
}
});
smartObject.firstName = 'John';
smartObject.lastName = 'Doe';
console.log(smartObject.fullName); // "John Doe"
console.log(smartObject.status); // "active"3. API调用的动态方法
const api = new Proxy({}, {
get(target, property) {
// 动态生成API方法
return function(...args) {
const method = property.startsWith('get') ? 'GET' :
property.startsWith('post') ? 'POST' :
property.startsWith('put') ? 'PUT' :
property.startsWith('delete') ? 'DELETE' : 'GET';
const endpoint = property
.replace(/^(get|post|put|delete)/, '')
.replace(/([A-Z])/g, '/$1')
.toLowerCase();
console.log(`${method} ${endpoint}`, args);
// 这里可以实际发起HTTP请求
return Promise.resolve({ method, endpoint, args });
};
}
});
// 动态API调用
api.getUsers(); // GET /users []
api.postUser({name: 'John'}); // POST /user [{name: 'John'}]
api.getUserById(123); // GET /user/by/id [123]4. 观察者模式
function createObservable(target, onChange) {
return new Proxy(target, {
set(obj, property, value) {
const oldValue = obj[property];
obj[property] = value;
// 触发变化回调
onChange(property, value, oldValue);
return true;
},
deleteProperty(obj, property) {
const oldValue = obj[property];
delete obj[property];
onChange(property, undefined, oldValue);
return true;
}
});
}
const data = createObservable({}, (property, newValue, oldValue) => {
console.log(`${property}: ${oldValue} -> ${newValue}`);
});
data.name = 'Alice'; // name: undefined -> Alice
data.age = 25; // age: undefined -> 25
data.name = 'Bob'; // name: Alice -> Bob
delete data.age; // age: 25 -> undefined5. 数组的负索引访问
function createArray(arr) {
return new Proxy(arr, {
get(target, property) {
// 处理负索引
if (typeof property === 'string' && /^-\d+$/.test(property)) {
const index = target.length + parseInt(property);
return target[index];
}
return target[property];
}
});
}
const arr = createArray([1, 2, 3, 4, 5]);
console.log(arr[-1]); // 5 (最后一个元素)
console.log(arr[-2]); // 4 (倒数第二个元素)
console.log(arr[0]); // 1 (正常索引)Proxy vs Object.defineProperty
1. 功能对比
// Object.defineProperty - 只能拦截属性访问
const obj1 = {};
Object.defineProperty(obj1, 'name', {
get() { console.log('get name'); return this._name; },
set(value) { console.log('set name'); this._name = value; }
});
// Proxy - 可以拦截更多操作
const obj2 = new Proxy({}, {
get(target, property) { console.log(`get ${property}`); return target[property]; },
set(target, property, value) { console.log(`set ${property}`); target[property] = value; return true; },
has(target, property) { console.log(`has ${property}`); return property in target; },
deleteProperty(target, property) { console.log(`delete ${property}`); delete target[property]; return true; }
});2. 数组监听对比
// Object.defineProperty - 无法监听数组变化
const arr1 = [1, 2, 3];
// 无法直接监听 arr1.push(), arr1[0] = 5 等操作
// Proxy - 可以完美监听数组
const arr2 = new Proxy([1, 2, 3], {
set(target, property, value) {
console.log(`数组变化: ${property} = ${value}`);
target[property] = value;
return true;
}
});
arr2.push(4); // 数组变化: 3 = 4, 数组变化: length = 4
arr2[0] = 10; // 数组变化: 0 = 10注意事项和限制
1. 性能考虑
// Proxy会有性能开销
const obj = { x: 1 };
const proxy = new Proxy(obj, {
get(target, property) {
// 每次访问都会执行这个函数
return target[property];
}
});
// 频繁访问时,直接访问原对象会更快
console.time('direct');
for (let i = 0; i < 1000000; i++) {
obj.x;
}
console.timeEnd('direct');
console.time('proxy');
for (let i = 0; i < 1000000; i++) {
proxy.x;
}
console.timeEnd('proxy');2. 不可撤销的代理
// 普通Proxy无法撤销
const proxy1 = new Proxy({}, {});
// 可撤销的Proxy
const { proxy: proxy2, revoke } = Proxy.revocable({}, {});
console.log(proxy2.x); // 正常访问
revoke(); // 撤销代理
// console.log(proxy2.x); // TypeError: Cannot perform 'get' on a proxy that has been revokedProxy是一个非常强大的特性,可以用来实现很多高级功能,如数据绑定、API包装、调试工具等。但也要注意性能影响和浏览器兼容性(IE不支持)。