Proxy

Proxy

允许你创建一个代理对象来拦截并自定义基本操作(属性查找、赋值、枚举、函数调用等)

怎么用

1
const proxy = new Proxy(target, handler);
  • target: 要代理的目标对象。
  • handler: 一个包含 get/set 等拦截方法的对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let target = {};

let handler = {
get: function(obj, prop) {
if (prop in obj) {
console.log(`Getting ${prop}: ${obj[prop]}`);
return obj[prop];
} else {
console.log(`${prop} 属性不存在`);
return undefined;
}
},
set: function(obj, prop, value) {
console.log(`Setting ${prop} to ${value}`);
obj[prop] = value;
}
};
  • get(target, property, receiver):拦截对象属性的读取操作。
  • set(target, property, value, receiver):拦截对象属性的设置操作。
  • has(target, propKey):拦截 propKey in proxy 的操作。
  • deleteProperty(target, propKey):拦截 delete proxy[propKey] 的操作。
  • apply(target, thisArg, argumentsList):拦截函数调用
  • construct(target, argumentsList, newTarget):拦截 new 操作符。
1
2
3
proxy.name = "Alice"; // 输出: Setting name to Alice
console.log(proxy.name); // 输出: Getting name: Alice
console.log(proxy.age); // 输出: age 属性不存在

使用场景

数据验证

可以在 set 拦截器中添加验证逻辑,确保只有符合特定条件的数据才能被设置到对象中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let validator = {
set: function(obj, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('The age is not an integer');
}
if (value > 200) {
throw new RangeError('The age seems invalid');
}
}
obj[prop] = value;
}
};

let person = new Proxy({}, validator);
person.age = 100; // 正常
person.age = 'young'; // 抛出 TypeError

日志记录

可以使用 getset 拦截器记录对对象的访问和修改情况。

性能监控

通过代理对象的方法调用,可以记录每次调用的时间消耗,用于性能分析。

私有属性管理

通过代理隐藏或保护对象的某些属性,使其不能被外部直接访问或修改。

撤销对象引用

使用 Proxy.revocable() 方法可以安全地撤销对某个对象的引用。

1
2
3
4
5
let {proxy, revoke} = Proxy.revocable({}, {});
proxy.name = "Alice";
console.log(proxy.name); // 输出: Alice
revoke(); // 撤销对 name 的引用
console.log(proxy.name); // 抛出 TypeError 错误

手写Proxy实现数组响应式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function reactiveArray(refArr, onChange) {
const hander = {
set(target, prop, value) {
target[prop] = value
if (prop != 'length') {
onChange(target)
}
return true
},
deleteProperty(target, prop) {
delete target[prop]
onChange(target.slice())
return true
},
}
return new Proxy(refArr, hander)
}
1
2
3
4
5
6
7
8
9
10
11
const myArray = reactiveArray([1, 2, 3], (newArr) => {
console.log('数组发生变化了', newArr)
})

myArray.push(4) // 数组发生变化了 [1, 2, 3, 4]
myArray[0] = 5 // 数组发生变化了 [5, 2, 3, 4]
myArray.pop() // 数组发生变化了 [5, 2, 3]
myArray.pop() // 数组发生变化了 [5, 2]
myArray.pop() // 数组发生变化了 [5]
myArray.pop() // 数组发生变化了 [空]
myArray.pop()