Proxy构造函数和Reflect有什么区别?

What is the difference between Proxy constructor and Reflect?

Reflect and Proxy有什么显着差异吗?

根据记录,它们似乎具有几乎相同的功能,除了:

如果上面的列表总结了所有差异,那么两者兼而有之的理由是什么?

Reflect 和 Proxy 的用途和功能完全不同。

MDN describes Proxy in that way:

The Proxy object is used to define custom behavior for fundamental operations (e.g. property lookup, assignment, enumeration, function invocation, etc).

And Reflect in that way:

Reflect is a built-in object that provides methods for interceptable JavaScript operations. The methods are the same as those of proxy handlers.

我知道你可能已经读过了,所以我将用一个例子来进一步解释它。

假设您有一个对象:

const obj = {
  a: 'foo',
  b: 'bar',
};

您可以使用 属性 访问器访问 属性 a

console.log(obj.a); // 'foo'

您可以使用 Reflect.get() 方法执行相同的操作:

console.log(Reflect.get(obj, 'a')); // 'foo'

您还可以使用 Proxy 构造函数创建该对象的代理。我们将使用 get 处理程序拦截所有 属性 查找。

const proxy = new Proxy(obj, {
  get(target, property) {
    return property in target ? target[property] : 'default';
  },
});

现在使用 属性 访问器或 Reflect.get() 获取未定义的 属性 结果字符串 'default':

console.log(proxy.c); // 'default'
console.log(Reflect.get(proxy, 'c')); // 'default'

Proxy 和 Reflect 可以很好地协同工作。例如,您可以使用 Reflect:

创建一个带有无操作 get 处理程序的代理
new Proxy(obj, {
  get: Reflect.get,
});

Proxy 是一个对象的包装器,它将对它的操作转发给对象,可以选择捕获其中的一些操作。 Proxy 对象允许您创建一个可用于代替原始对象的对象,但它可能会重新定义基本的对象操作,例如获取、设置和定义属性。

Reflect API 旨在补充 Proxy。 [[Get]][[Set]]等内部方法是specification-only,不能直接调用。 Reflect 对象使这成为可能。

所以 Proxy 是一个包装器,可用于拦截对象上的 [[Get]][[Set]] 等基本操作,而 Reflect 为我们提供了围绕这些基本操作的最小包装器[[Get]][[Set]] 等操作,以便我们可以直接调用它们(通常从陷阱内部)。

------------ Reflect如何补充Proxy------------

对于每个可被 Proxy 捕获的内部方法,在 Reflect 中都有一个对应的方法,其名称和参数与 Proxy 陷阱相同。 (!重要)

让我们看看这个例子来证明它是如何有用的。

let user = {
  _name: "Guest",
  get name() {
    return this._name;
  }
};

let userProxy = new Proxy(user, {
  get(target, prop, receiver) {
    return Reflect.get(target, prop, receiver);
    // return target[prop];
  }
});

alert(userProxy.name); // Guest

在上面的示例中,在 get 陷阱内,return Reflect.get(target, prop, receiver);return target[prop]; 将打印相同的输出 (Guest)。

让我们举一个稍微复杂一点的例子来说明为什么 Reflect.get 更好以及为什么 get/set 有第三个参数 receiver.

让我们创建一个继承自 user 的对象 admin:

let user = {
  _name: "Guest",
  get name() {
    return this._name;
  }
};

let userProxy = new Proxy(user, {
  get(target, prop, receiver) {
    return target[prop]; // (*) target = user
  }
});

let admin = {
  __proto__: userProxy,
  _name: "Admin"
};

// Expected: Admin
alert(admin.name); // outputs: Guest (?!?)

阅读admin.name应该return"Admin",而不是"Guest"

问题实际上出在代理中,在行(*)

  1. 当我们读取 admin.name 时,由于 admin 对象没有自己的 属性,搜索将转到其原型。
  2. 原型是userProxy
  3. 当从代理读取name属性时,它的get陷阱触发并且return从原始对象target[prop]中删除它(*)。当 prop 是 getter 时,对 target[prop] 的调用会在上下文 this=target 中运行其代码。所以结果是 this._name 来自原始对象 target,即:来自 user.

要解决此问题,我们需要将正确的 this 传递给 getter。 receiver,get trap 的第三个参数将正确的 this 传递给 getter(在我们的例子中是 admin)。对于常规函数,我们可以使用 call/apply 绑定 this 值,但我们不能对 getter 执行相同的操作,因为它不是 called,只是访问。

这是Reflect有用的地方。请记住,对于每个可被 Proxy 捕获的内部方法,在 Reflect 中都有一个对应的方法,其名称和参数与代理陷阱相同。[​​=64=]

let user = {
  _name: "Guest",
  get name() {
    return this._name;
  }
};

let userProxy = new Proxy(user, {
  get(target, prop, receiver) { // receiver = admin
    return Reflect.get(target, prop, receiver); // (*)
  }
});


let admin = {
  __proto__: userProxy,
  _name: "Admin"
};

alert(admin.name); // Admin

详细解释请参考 Ilya Kantor 的这篇精彩文章:Proxy and Reflect