Proxy
语法:
const p = new Proxy(target, handler)target是被Proxy包装的目标对象(可以是任何类型的对象,包括原生数组、函数,甚至另一个代理)handler定义了若干属性的对象,通常属性值为函数,用于定义在对Proxy实例p执行各种操作时的行为。
其中Proxy还有个静态方法——Proxy.revocable,用于创建可撤销的Proxy对象。
handler处理器对象
handler对象是一个容纳若干特定属性的占位符对象,包含了Proxy的各种捕获器(trap,另译为陷阱🪤)
所有捕获器都是可选的,不设置则保留源对象的默认行为。
handler.getPrototypeOf()Object.getPrototypeOf方法的捕捉器。
handler.setPrototypeOf()Object.setPrototypeOf方法的捕捉器。
handler.isExtensible()Object.isExtensible方法的捕捉器。
handler.preventExtensions()Object.preventExtensions方法的捕捉器。
handler.getOwnPropertyDescriptor()Object.getOwnPropertyDescriptor方法的捕捉器。
handler.defineProperty()Object.defineProperty方法的捕捉器。
handler.has()in操作符的捕捉器。
handler.get()- 属性读取操作的捕捉器。
handler.set()- 属性设置操作的捕捉器。
handler.deleteProperty()delete操作符的捕捉器。
handler.ownKeys()Object.getOwnPropertyNames方法和Object.getOwnPropertySymbols方法的捕捉器。
handler.apply()- 函数调用操作的捕捉器。
handler.construct()new操作符的捕捉器。
| Operation | Intercepted as |
|---|---|
proxy[name] | handler.get(proxy, name) |
proxy[name] = val | handler.set(proxy, name, val) |
name in proxy | handler.has(name) |
delete proxy[name] | handler.delete(name) |
for (let name in proxy) {…} | handler.iterate() |
Object.keys(proxy) | handler.keys() |
Reflect
Reflect是一个内置对象,提供了拦截JavaScript操作的方法,这些方法与Proxy相同。
单独使用Reflect用处不大,与直接执行JavaScript操作相比,有如下用处:
- 从
Reflect对象上可以拿到语言内部的方法,如Object.defineProperty - 操作对象失败时返回
false - 操作对象从命令式变为函数式
- 操作更易读
// 旧写法
try {
Object.defineProperty(target, property, attributes);
} catch (err) {}
// 新写法
if (Reflect.defineProperty(target, property, attributes)){}
else {}
// 老写法
"assign" in Object; // true
// 新写法
Reflect.has(Object, "assign"); //
// 老写法
Function.prototype.apply.call(Math.floor, undefined, [1.75]) // 1
// 新写法
Reflect.apply(Math.floor, undefined, [1.75]) // 1Reflect在Proxy中的作用
就功能而言,Reflect.get()和Reflect.set()方法和直接对象赋值没有区别,都是可以互相替代的。
但在实践中,Proxy一般搭配Reflect使用,原因有以下三点:
Reflect提供的静态方法和Proxy的handler参数方法一模一样Proxy get/set方法需要的返回值正是Reflect的get/set方法的返回值。可天然配合使用,比直接对象赋值/取值更方便和准确receiver参数具有不可替代性,见下方⬇️
关于receiver参数
Proxy handler的get/set方法都提供了第三个参数receiver,指代Proxy或继承Proxy的对象(但handler的set方法也可能在原型链上,或以其他方式被间接调用)。
举个例子:
const parent = {
name: 'parent name',
get value() {
return this.name;
},
};
const handler = {
get(target, key, receiver) {
return Reflect.get(target, key);
// 这里相当于 return target[key]
},
};
const proxy = new Proxy(parent, handler);
const child = {
name: 'child name',
};
// 设置obj继承与parent的代理对象proxy
Object.setPrototypeOf(child, proxy);
console.log(child.value); // parent name分析以上代码:
- 获取
child.value时,child本身没有value属性 - 在上一步显式指定了
child的原型对象,此时应从proxy上找value属性 proxy是代理对象,本身通过handler.get方法获取value属性- 在
handler.get中直接从目标对象中获取value属性,此时目标对象是parent,返回的是parent.value
可看到这与我们的预期不符,当访问child.value时,因原型对象上有value且根据parent上的定义,应返回自身的name属性即child.name
要解决该问题,只需在Reflect.get时传递receiver:
const parent = {
name: 'parent name',
get value() {
return this.name;
},
};
const handler = {
get(target, key, receiver) {
return Reflect.get(target, key, receiver);
},
};
const proxy = new Proxy(parent, handler);
const child = {
name: 'child name',
};
Object.setPrototypeOf(child, proxy);
console.log(child.value); // child name在Reflect.get的定义中,receiver表示:
如果target对象指定了getter,receiver则为调用时的this值