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
Reflec
t是一个内置对象,提供了拦截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]) // 1
Reflect在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
值