【问题标题】:What is the difference between Proxy constructor and Reflect?代理构造函数和反射有什么区别?
【发布时间】:2016-08-27 09:51:59
【问题描述】:

ReflectProxy 之间有什么显着差异吗?

从记录来看,它们似乎具有几乎相同的功能,除了:

  • 反映一次只能指定一个陷阱。
  • 代理是revocable
  • 代理是构造函数。

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

【问题讨论】:

    标签: javascript reflection ecmascript-6 es6-proxy


    【解决方案1】:

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

    MDN describes Proxy in that way:

    Proxy 对象用于定义基本操作(例如属性查找、赋值、枚举、函数调用等)的自定义行为。

    And Reflect in that way:

    Reflect 是一个内置对象,它为可拦截的 JavaScript 操作提供方法。方法同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'
    

    代理和反射可以很好地协同工作。例如,您可以使用 Reflect 创建一个带有无操作 get 处理程序的代理:

    new Proxy(obj, {
      get: Reflect.get,
    });
    

    【讨论】:

      【解决方案2】:

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

      Reflect API 旨在补充代理。 [[Get]][[Set]] 等内部方法只是规范,不能直接调用。 Reflect 对象使这成为可能。

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

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

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

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

      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

      让我们创建一个对象admin,它继承自user

      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应该返回"Admin",而不是"Guest"

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

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

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

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

      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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-08-30
        • 1970-01-01
        • 1970-01-01
        • 2011-10-17
        • 1970-01-01
        • 1970-01-01
        • 2015-02-13
        • 1970-01-01
        相关资源
        最近更新 更多