Proxy & Reflect
Icon could not be loaded
6 min read
#writings#JavaScript

Proxy

语法:

const p = new Proxy(target, handler)

其中Proxy还有个静态方法——Proxy.revocable,用于创建可撤销的Proxy对象。

handler处理器对象

handler对象是一个容纳若干特定属性的占位符对象,包含了Proxy的各种捕获器(trap,另译为陷阱🪤

所有捕获器都是可选的,不设置则保留源对象的默认行为。

OperationIntercepted as
proxy[name]handler.get(proxy, name)
proxy[name] = valhandler.set(proxy, name, val)
name in proxyhandler.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操作相比,有如下用处:

  1. Reflect对象上可以拿到语言内部的方法,如Object.defineProperty
  2. 操作对象失败时返回false
  3. 操作对象从命令式变为函数式
  4. 操作更易读
// 旧写法
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使用,原因有以下三点:

  1. Reflect提供的静态方法和Proxyhandler参数方法一模一样
  2. Proxy get/set方法需要的返回值正是Reflectget/set方法的返回值。可天然配合使用,比直接对象赋值/取值更方便和准确
  3. receiver参数具有不可替代性,见下方⬇️

关于receiver参数

Proxy handlerget/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

分析以上代码:

  1. 获取child.value时,child本身没有value属性
  2. 在上一步显式指定了child的原型对象,此时应从proxy上找value属性
  3. proxy是代理对象,本身通过handler.get方法获取value属性
  4. 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对象指定了getterreceiver则为调用时的this