ES6 Proxy 的基本用法
一、Proxy 是什么?
Proxy(代理)是 ES6 引入的一个新特性,用于创建一个对象的代理,从而可以拦截并自定义该对象的基本操作。
二、基本语法
javascript
const proxy = new Proxy(target, handler);
target:要包装的目标对象handler:包含拦截器(traps)的对象
三、常用的拦截方法
1. get() - 拦截属性读取
javascript
const person = { name: 'Alice', age: 25 };
const proxy = new Proxy(person, {
get(target, property) {
if (property in target) {
return target[property];
}
return `属性 ${property} 不存在`;
}
});
console.log(proxy.name); // 'Alice'
console.log(proxy.address); // '属性 address 不存在'
2. set() - 拦截属性设置
javascript
const validator = {
set(target, property, value) {
if (property === 'age') {
if (typeof value !== 'number') {
throw new Error('年龄必须是数字');
}
if (value < 0) {
throw new Error('年龄不能为负数');
}
}
target[property] = value;
return true; // 表示设置成功
}
};
const person = new Proxy({}, validator);
person.age = 25; // 正常
// person.age = -5; // Error: 年龄不能为负数
3. has() - 拦截 in 操作符
javascript
const handler = {
has(target, property) {
if (property.startsWith('_')) {
return false; // 隐藏私有属性
}
return property in target;
}
};
const obj = { name: 'Alice', _password: '123' };
const proxy = new Proxy(obj, handler);
console.log('name' in proxy); // true
console.log('_password' in proxy); // false
4. apply() - 拦截函数调用
javascript
function sum(a, b) {
return a + b;
}
const proxy = new Proxy(sum, {
apply(target, thisArg, argumentsList) {
console.log(`调用函数: ${target.name}`);
console.log(`参数: ${argumentsList}`);
return target.apply(thisArg, argumentsList);
}
});
console.log(proxy(1, 2)); // 3
// 输出:
// 调用函数: sum
// 参数: 1,2
5. construct() - 拦截 new 操作符
javascript
class Person {
constructor(name) {
this.name = name;
}
}
const ProxyPerson = new Proxy(Person, {
construct(target, args) {
console.log(`创建 ${target.name} 实例`);
return new target(...args);
}
});
const p = new ProxyPerson('Alice'); // 输出: 创建 Person 实例
四、实用示例
1. 数据验证
javascript
const userValidator = {
set(target, prop, value) {
if (prop === 'email' && !/^\S+@\S+\.\S+$/.test(value)) {
throw new Error('邮箱格式不正确');
}
if (prop === 'age' && (value < 0 || value > 150)) {
throw new Error('年龄无效');
}
target[prop] = value;
return true;
}
};
const user = new Proxy({}, userValidator);
user.email = 'test@example.com'; // 正常
// user.email = 'invalid-email'; // Error
2. 数组负索引支持
javascript
function createNegativeArray(array) {
return new Proxy(array, {
get(target, prop) {
const index = Number(prop);
if (index < 0) {
return target[target.length + index];
}
return target[prop];
}
});
}
const arr = createNegativeArray([1, 2, 3, 4, 5]);
console.log(arr[-1]); // 5
console.log(arr[-2]); // 4
3. 计算属性
javascript
const expensiveComputation = {
cache: new Map(),
get(target, property) {
if (property === 'expensiveData') {
if (!this.cache.has('expensiveData')) {
console.log('执行昂贵计算...');
const result = /* 昂贵计算 */ '计算结果';
this.cache.set('expensiveData', result);
}
return this.cache.get('expensiveData');
}
return target[property];
}
};
const obj = new Proxy({}, expensiveComputation);
console.log(obj.expensiveData); // 执行昂贵计算... 计算结果
console.log(obj.expensiveData); // 计算结果(从缓存读取)
4. 方法链式调用
javascript
const chainable = {
get(target, prop) {
if (prop in target) {
return target[prop];
}
return new Proxy(() => {}, {
apply() {
console.log(`调用了方法: ${prop}`);
return proxy; // 返回代理以支持链式调用
}
});
}
};
const obj = new Proxy({}, chainable);
obj.say().hello().world();
// 输出:
// 调用了方法: say
// 调用了方法: hello
// 调用了方法: world
五、注意事项
- 性能:Proxy 操作比直接对象操作稍慢,在性能关键代码中慎用
- this 绑定:Proxy 中的 this 指向代理对象,而非目标对象
- 不可撤销代理 :使用
Proxy.revocable()创建可撤销的代理
javascript
const { proxy, revoke } = Proxy.revocable(target, handler);
revoke(); // 撤销代理
六、浏览器兼容性
- 现代浏览器基本都支持
- Node.js 6.0+ 支持
Proxy 是 JavaScript 元编程的强大工具,可以用于实现各种高级功能,如数据绑定、验证、日志、性能优化等。
Vue 3 中使用 Proxy 来替代 Vue 2 中的 Object.defineProperty,实现了响应式系统。以后可以基于这个方面,全面分析一下Vue3 怎么实现的响应式系统。里面牵涉到Proxy用法,依赖收集和触发更新,副作用函数,数组的处理,reactive的实现逻辑,watch、ref、shallowReactive、computed等,挺有意思的。