【问题标题】:How to test if an object is a Proxy?如何测试一个对象是否是代理?
【发布时间】:2016-04-02 11:25:49
【问题描述】:

我想测试一个 JavaScript 对象是否是 Proxy。简单的方法

if (obj instanceof Proxy) ...

在这里不起作用,遍历Proxy.prototype 的原型链也不起作用,因为所有相关操作都得到了底层目标的有效支持。

是否可以测试任意对象是否为代理?

【问题讨论】:

  • 也许代理代理?我的意思是在任何涉及代理的脚本之前代理代理函数..
  • 代理的目的难道不是您无法将它们与“正常”对象区分开来吗?为什么要测试这个?
  • @Bergi 好吧,这当然不是代理的 主要 目的。剩下的,我有点惊讶你无法找出这个测试的用例。
  • 我写了一个小提琴来澄清这个想法,...jsfiddle.net/ycw7788/uauoxn7o
  • 这行得通吗? function isProxy(o) { if (typeof Proxy !== 'function') return false;尝试 { o instanceof 代理;返回假; } 捕捉 () { 返回真; } }

标签: javascript ecmascript-6 proxy-classes


【解决方案1】:

在我当前的项目中,我还需要一种方法来定义某物是否已经是代理,主要是因为我不想在代理上启动代理。为此,我只是在我的处理程序中添加了一个 getter,如果请求的变量是“__Proxy”,它将返回 true:

function _observe(obj) {
  if (obj.__isProxy === undefined) {
    var ret = new Proxy(obj || {}, {
      set: (target, key, value) => {
        /// act on the change
        return true;
      },
      get: (target, key) => {
        if (key !== "__isProxy") {
          return target[key];
        }

        return true;
      }
    });
    return ret;
  }

  return obj;
}

可能不是最好的解决方案,但我认为这是一个优雅的解决方案,在序列化时也不会弹出。

【讨论】:

  • 谢谢!我正在做某种深度代理,我也遇到了这个问题。
  • 太好了,我正在使用这种方法,但有一点小改动: const IS_PROXY = Symbol("is-proxy"); ... if (!obj[IS_PROXY])
  • @MatthewJoelRodríguezLlanos 只需确保符号存储在某个闭包中,因为Symbol("is-proxy") !== Symbol("is-proxy"),或者您使用Symbol.for
  • 这仅在您控制创建代理陷阱时才有效。
【解决方案2】:

在 Node.js 10 中,您可以使用 util.types.isProxy

例如:

const target = {};
const proxy = new Proxy(target, {});
util.types.isProxy(target);  // Returns false
util.types.isProxy(proxy);  // Returns true

【讨论】:

    【解决方案3】:

    创建一个新符号:

    let isProxy = Symbol("isProxy")
    

    在代理处理程序的 get 方法中,您可以检查 key 是否是您的符号,然后检查 return true

    get(target, key)
    {
        if (key === isProxy)
            return true;
    
        // normal get handler code here
    }
    

    然后您可以使用以下代码检查一个对象是否是您的代理之一:

    if (myObject[isProxy]) ...
    

    【讨论】:

    • 有趣!只是给其他人的注释,这只有在您控制代理获取陷阱时才有效。否则,如果您不拥有该对象,这将不起作用。
    • @snewcomer 谢谢。没错,我认为这满足了常见的用例。 (我个人认为您不应该检查外来对象是否是代理,因为您正在编写特定于实现的代码并首先覆盖代理的整个抽象性质/目的)。
    【解决方案4】:

    来自http://www.2ality.com/2014/12/es6-proxies.html

    无法确定对象是否为代理(透明虚拟化)。

    【讨论】:

    • 我不会链接以注释“这篇博文已过时”开头的文章 :-) 但是,exploringjs.com/es6/ch_proxies.html 声明完全相同
    • @BrassApparatus 其实他的评论和我的回答都错了,详细here
    【解决方案5】:

    instanceof Proxy添加“支持”:

    我不推荐,但如果你想添加对instanceof 的支持,你可以在实例化任何代理之前执行以下操作:

    (() => {
      var proxyInstances = new WeakSet()
      
      // Optionally save the original in global scope:
      originalProxy = Proxy
    
      Proxy = new Proxy(Proxy, {
        construct(target, args) {
          var newProxy = new originalProxy(...args)
          proxyInstances.add(newProxy)
          return newProxy
        },
        get(obj, prop) {
          if (prop == Symbol.hasInstance) {
            return (instance) => {
              return proxyInstances.has(instance)
            }
          }
          return Reflect.get(...arguments)
        }
      })
    })()
    
    // Demo:
    
    var a = new Proxy({}, {})
    console.log(a instanceof Proxy) // true
    delete a
    
    var a = new originalProxy({}, {})
    console.log(a instanceof Proxy) // false
    delete a

    【讨论】:

    • 老哥,听说你喜欢代理,所以我把代理放在你的代理里,这样你做代理后就可以识别代理了。
    【解决方案6】:

    事实上,有一种解决方法可以确定对象是否为代理,它基于几个假设。首先,当页面可以启动不安全的扩展时,可以通过 C++ 扩展或浏览器中的特权网页轻松解决 node.js 环境的代理确定问题。其次,代理是相对较新的功能,因此它在旧浏览器中不存在 - 因此解决方案仅适用于现代浏览器。

    JS 引擎不能克隆函数(因为它们绑定到激活上下文和其他一些原因),但是根据定义,代理对象由包装处理程序组成。因此,要确定对象是否为代理,启动强制对象克隆就足够了。可以通过postMessage 函数完成。

    如果对象是代理,即使它不包含任何功能,它也将无法复制。例如,Edge 和 Chrome 在尝试发布 Proxy 对象时会产生以下错误:[object DOMException]: {code: 25, message: "DataCloneError", name: "DataCloneError"}Failed to execute 'postMessage' on 'Window': [object Object] could not be cloned.

    【讨论】:

    • 有趣。我想知道是否有办法让这个方法也适用于包含函数、DOM 元素和其他不可克隆的东西的对象。
    • +1 用于在许多情况下找到一种真正的解决方案。结果证明,以同样的方式,代理的 Event 对象不能被分派(即dispatchEvent)并且代理的 DOM 元素不能附加到 DOM。可能还有其他独特的节点(如音频上下文节点)。
    • @Codesmith 在想知道为什么代理事件不会调度之后,我发现自己来到了这里。
    • "所以要确定对象是否为代理,启动强制对象克隆就足够了。"有趣的。我认为根据定义代理应该将函数抽象为看起来只是属性-值对的东西。所以我会认为强制克隆相当于迭代代理中的每个属性并在克隆时克隆它的值。 (因为这对 javascript 用户来说应该是这样的)
    • @DavidCallanan 听起来像“leaky abstraction
    【解决方案7】:

    使用 window.postMessage() 和 try-catch 来获得提示

    postMessage 无法序列化与structured clone algorithm 不兼容的对象,例如 Proxy。

    function shouldBeCloneable(o) {
        const type = typeof o;
        return (
            type === "undefined" ||
            o === null ||
            type === "boolean" ||
            type === "number" ||
            type === "string" ||
            o instanceof Date ||
            o instanceof RegExp ||
            o instanceof Blob ||
            o instanceof File ||
            o instanceof FileList ||
            o instanceof ArrayBuffer ||
            o instanceof ImageData ||
            o instanceof ImageBitmap ||
            o instanceof Array ||
            o instanceof Map ||
            o instanceof Set
        );
    }
    
    function isCloneable(obj) {
        try {
            postMessage(obj, "*");
        } catch (error) {
            if (error?.code === 25) return false; // DATA_CLONE_ERR
        }
    
        return true;
    }
    
    function isProxy(obj){
        const _shouldBeCloneable = shouldBeCloneable(obj);
        const _isCloneable = isCloneable(obj);
    
        if(_isCloneable) return false;
        if(!_shouldBeCloneable) return "maybe";
        
        return _shouldBeCloneable && !_isCloneable;
    }
    
    console.log("proxied {}", isProxy(new Proxy({},{})));
    console.log("{}", isProxy({}));
    
    console.log("proxied []", isProxy(new Proxy([],{})));
    console.log("[]", isProxy([]));
    
    console.log("proxied function", isProxy(new Proxy(()=>{},{})));
    console.log("function", isProxy(()=>{}));
    
    console.log("proxied Map", isProxy(new Proxy(new Map(),{})));
    console.log("new Map()", isProxy(new Map()));
    
    class A{};
    console.log("proxied class", isProxy(new Proxy(A,{})));
    console.log("class", isProxy(A));
    
    console.log("proxied class instance", isProxy(new Proxy(new A(),{})));
    console.log("class instance", isProxy(new A()));
    

    【讨论】:

    • 有效!有了这个针对浏览器的解决方案,以及针对 NodeJS 的this other solution,我们已经涵盖了两个主要的上下文。 :)
    • 这只会告诉你对象是否不可克隆,对吧?所以这个非代理从你的函数返回 true:isProxy({a:()=>{}})。它应该被称为“isNotCloneable”。
    • 不适用于jsdomjest,因为它们有一个假的postMessage() 函数
    • 这个答案不正确。结构化克隆算法还会为 Function 对象和 DOM 节点抛出 DataCloneError。
    【解决方案8】:

    我发现的最佳方法是创建一组弱代理对象。您可以在构建和检查代理对象时递归地执行此操作。

        var myProxySet = new WeakSet();
        var myObj = new Proxy({},myValidator);
        myProxySet.add(myObj);
    
        if(myProxySet.has(myObj)) {
            // Working with a proxy object.
        }
    

    【讨论】:

      【解决方案9】:

      好像没有标准的方法,但是对于Firefox的特权代码你可以使用

      Components.utils.isProxy(object);
      

      例如:

      Components.utils.isProxy([]); // false
      Components.utils.isProxy(new Proxy([], {})); // true
      

      【讨论】:

        【解决方案10】:

        Matthew Brichacek 和 David Callanan 为您自己创建的 Proxy 给出了很好的答案,但如果不是这样,这里还有一些补充

        假设您有一个无法修改的外部函数创建代理

        const external_script = ()=>{
            return new Proxy({a:5},{})
        }
        

        在执行任何外部代码之前,我们可以重新定义代理构造函数并使用 Wea​​kSet 来存储代理,就像 Matthew Brichacek 所做的那样。 我不使用类,否则 Proxy 将有一个原型,并且可以检测到 Proxy 已更改。

        const proxy_set = new WeakSet()
        window.Proxy = new Proxy(Proxy,{
              construct(target, args) {
                const proxy = new target(...args)
                proxy_set.add(proxy)
                return proxy
              }
        })
        const a = external_script()
        console.log(proxy_set.has(a)) //true
        

        同样的方法,但使用像 David Callanan 这样的符号

          const is_proxy = Symbol('is_proxy')
          const old_Proxy = Proxy
          const handler = {
            has (target, key) {
              return (is_proxy === key) || (key in target)
            }
          }
          window.Proxy = new Proxy(Proxy,{
              construct(target, args) {
                  return new old_Proxy(new target(...args), handler)
              }
          })
          const a = external_script()
          console.log(is_proxy in a) //true
        

        我认为第一个更好,因为您只更改构造函数,而第二个创建代理的代理,而问题的目的是避免这种情况。

        如果代理是在 iframe 中创建的,它不起作用,因为我们只是为当前帧重新定义了代理。

        【讨论】:

        • 这是 IMO 最实用的解决方案。
        【解决方案11】:

        根据 JS 语言规范,无法检测某事物是否为 Proxy

        node 确实通过本机代码提供了一种机制,但我不推荐使用它 - 你不应该知道某个东西是否是 Proxy

        其他建议包装或隐藏全局 Proxy 的答案实际上不会跨领域工作(即 iframe、网络工作者、节点的 vm 模块、wasm 等)。

        【讨论】:

          【解决方案12】:

          代理对象有两种方式。一个是new Proxy,另一个是Proxy.revocable。我们可以监视它们,以便将代理对象记录到秘密列表中。然后我们通过检查是否确定一个对象是代理对象 它存在于秘密列表中。

          要监视函数,我们可以编写包装器或使用内置代理。后者表示使用Proxy代理new ProxyProxy.recovable,这里有一个fiddle来演示这个想法。

          要像 nodejs-v5.8.0 代理一样服务old Proxy API,我们可以通过使用Proxy.createFunction 代理Proxy.createProxy.createFunction 来应用相同的想法。

          【讨论】:

          • 此方法不适用于在其他领域创建的代理(如 iframe,或节点的 vm 模块),因为您无法在其他领域监视全局变量(如 Proxy)。
          • @LJHarb 你确定吗?在阅读您的评论后,我实际上尝试过,但在跨 iframe 领域替换 Proxy 对象时没有问题。我没有使用 vm 模块对其进行测试,但不明白为什么它不应该以同样的方式工作。
          • @GOTO0 我的意思是,除非您可以先在其他领域运行代码,否则您不能保证它有一个影子代理。特别是对于 iframe,我可以创建一个全新的并访问原始的。
          • @LJHarb 如果是这样,这是一个有效的观点,它同样适用于我在这里看到的大多数其他答案。很遗憾,您选择了最不显眼的答案来发表评论。
          • 我发布了一个答案;如果您觉得它有价值,请随时投票。
          【解决方案13】:

          我相信我找到了一种更安全的方法来检查该项目是否为代理。这个答案的灵感来自Xabre's answer

          function getProxy(target, property) {
              if (property === Symbol.for("__isProxy")) return true;
              if (property === Symbol.for("__target")) return target;
              return target[property];
          }
          
          function setProxy(target, property, value) {
              if (property === Symbol.for("__isProxy")) throw new Error("You cannot set the value of '__isProxy'");
              if (property === Symbol.for("__target")) throw new Error("You cannot set the value of '__target'");
              if (target[property !== value]) target[property] = value;
              return true;
          }
          
          function isProxy(proxy) {
              return proxy == null ? false : !!proxy[Symbol.for("__isProxy")];
          }
          
          function getTarget(proxy) {
              return isProxy(proxy) ? proxy[Symbol.for("__target")] : proxy;
          }
          
          function updateProxy(values, property) {
              values[property] = new Proxy(getTarget(values[property]), {
                  set: setProxy,
                  get: getProxy
              });
          }
          

          基本上我所做的是,我没有将__isProxy 字段添加到目标,而是在代理的getter 中添加了这个检查:if (property === Symbol.for("__isProxy")) return true;。这样,如果您使用 for-in 循环或Object.keysObject.hasOwnProperty,__isProxy 将不存在。

          不幸的是,即使您可以设置 __isProxy 的值,由于对 getter 的检查,您将永远无法检索它。因此,您应该在设置字段时抛出错误。

          您也可以使用Symbol 来检查变量是否是代理,如果您认为它可能想要使用__isProxy 作为不同的属性。

          最后,我还为代理的目标添加了类似的功能,这也很难检索。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2019-07-27
            • 1970-01-01
            • 1970-01-01
            • 2013-07-03
            • 2012-06-30
            • 2021-02-16
            • 1970-01-01
            相关资源
            最近更新 更多