ChatGPT解决这个技术问题 Extra ChatGPT

How to test if an object is a Proxy?

I would like to test if a JavaScript object is a Proxy. The trivial approach

if (obj instanceof Proxy) ...

doesn't work here, nor does traversing the prototype chain for Proxy.prototype, since all relevant operations are effectively backed by the underlying target.

Is it possible to test if an arbitrary object is a Proxy?

Maybe proxy the Proxy? I mean proxy the Proxy function before any script involving Proxy..
Isn't the very purpose of proxies that you cannot distinguish them from a "normal" object? Why would you want to test this?
@Bergi well, certainly that's not the main purpose of proxies. For the rest, I'm a bit surprised you cannot figure out a use case for this test.
I wrote a fiddle to clarify the idea,... jsfiddle.net/ycw7788/uauoxn7o
would that work? function isProxy(o) { if (typeof Proxy !== 'function') return false; try { o instanceof Proxy; return false; } catch () { return true; } }

X
Xabre

In my current project I also needed a way of defining if something was already a Proxy, mainly because I didn't want to start a proxy on a proxy. For this I simply added a getter to my handler, which would return true if the requested variable was "__Proxy":

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; }

Might not be the best solution, but I think it's an elegant solution, which also doesn't pop up when serializing.


Thanks! I'm doing some sort of deep proxy, and I was getting into this problem as well.
Great, I'm using this approach but with a small twist: const IS_PROXY = Symbol("is-proxy"); ... if (!obj[IS_PROXY])
@MatthewJoelRodríguezLlanos Just ensure that the symbol is stored in some closure, because Symbol("is-proxy") !== Symbol("is-proxy"), or that you use Symbol.for
This only works if you are in control of creating the proxy trap.
G
GOTO 0

In Node.js 10 you can use util.types.isProxy.

For example:

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

D
David Callanan

Create a new symbol:

let isProxy = Symbol("isProxy")

Inside the get method of your proxy handler you can check if the key is your symbol and then return true:

get(target, key)
{
    if (key === isProxy)
        return true;

    // normal get handler code here
}

You can then check if an object is one of your proxies by using the following code:

if (myObject[isProxy]) ...

Interesting! Just a note for others, this will only work if you control the Proxy get trap. Otherwise this won't work if you don't own the object.
@snewcomer Thanks. That is right and I think this satisfies the common use-case. (I personally don't believe you should be checking if foreign objects are proxies because then you are writing implementation-specific code and overriding the whole abstract nature/purpose of proxies in the first place).
佚名

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

It is impossible to determine whether an object is a proxy or not (transparent virtualization).


I wouldn't link an article that starts with a note "this blog post is outdated" :-) However, exploringjs.com/es6/ch_proxies.html states exactly the same
@BrassApparatus Actually, it turns out that both his comment and my answer are wrong, as detailed here.
A
ADJenks

Adding 'support' for instanceof Proxy:

I don't recommend it, but If you want to add support for instanceof, you could do the following before instantiating any Proxies:

(() => { 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


Yo dawg, I heard you like proxies, so I put proxies in your proxies so you can identify proxies after you make proxies.
V
Vladislav Ihost

In fact, there is workaround for determine if object is proxy, which is based on several assumptions. Firstly, Proxy determination can be easily solved for node.js environment via C++ extensions or privileged web-page in browser, when page can launch unsecure extensions. Secondly, Proxy is relative new functionality, so it does not exist in old browsers - so solution works only in modern browsers.

JS engine can't clone functions (Since they have bindings to activation context and some other reasons), but Proxy object by definition consists of wrapper handlers. So to determine if object is proxy, it's enough to initiate force object cloning. In can be done via postMessage function.

If object is Proxy, it will failed to copy even it does not contain any functions. For example, Edge and Chrome produces following errors while try to post Proxy object: [object DOMException]: {code: 25, message: "DataCloneError", name: "DataCloneError"} and Failed to execute 'postMessage' on 'Window': [object Object] could not be cloned..


Interesting. I wonder if there's a way to make this method work also with objects that contain functions, DOM elements, and other unclonable stuff.
+1 for finding one true solution for many cases. Turns out, in the same way, proxied Event objects cannot be dispatched (i.e., dispatchEvent) and proxied DOM elements cannot be attached to the DOM. It's possible there are other unique ones too (like audio context nodes).
@Codesmith I found myself here after wondering why proxied events wouldn't dispatch.
"So to determine if object is proxy, it's enough to initiate force object cloning." Interesting. I thought by definition proxy was supposed to abstract away functions into what appears to just be property-value pairs. So I would've thought a force clone would've been equivalent to iterating each property in the proxy and cloning its value at clone-time. (Because that's what it's supposed to appear like to the javascript user)
@DavidCallanan It sounds like a "leaky abstraction"
E
Eylon Sultan

Use window.postMessage() with try-catch to get a hint

postMessage cannot serialize objects which incompatible with structured clone algorithm, like Proxy.

function shouldBeCloneable(o) {
    const type = typeof o;
    return (
        o?.constructor === ({}).constructor ||
        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()));

It works! With this solution for browsers, and this other solution for NodeJS, we have the two main contexts covered. :)
This only tells you if the object is not cloneable, right? So this non-proxy, returns true from your function: isProxy({a:()=>{}}). It should just be called "isNotCloneable".
Doesn't work with jsdom or jest since they have a fake postMessage() function
This answer is not correct. Structured clone algorithm also throws DataCloneError for Function objects and DOM nodes.
Is there a reason shouldBeCloneable({}) is false? It seems to me that you should check for o?.constructor === ({}).constructor...
M
Matthew Brichacek

The best method I have found is creating a weak set of the proxy objects. You can do this recursively when you are building and checking your proxied objects.

    var myProxySet = new WeakSet();
    var myObj = new Proxy({},myValidator);
    myProxySet.add(myObj);

    if(myProxySet.has(myObj)) {
        // Working with a proxy object.
    }

b
bormat

Matthew Brichacek and David Callanan give good answers for Proxy you create yourself but if it is not the case here are some additions

Imagine you have an external function creating Proxy that you can't modify

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

Before any externals code executions, we can redefine the proxy constructor and use a WeakSet to store proxy as Matthew Brichacek does. I don't use a class because otherwise Proxy will have a prototype and it will be detectable that Proxy has been changed.

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

Same method but with Symbol like 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

I think the first is better because you only change the constructor while the second creates a proxy of a proxy while the purpose of the question was to avoid this.

It does not work if the proxy is created inside an iframe because we only have redefined the proxy for the current frame.


this is the most practical solution IMO.
O
Oriol

It seems there is no standard way, but for Firefox privileged code you can use

Components.utils.isProxy(object);

For example:

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

L
LJHarb

It is impossible to detect if something is a Proxy according to the JS language specification.

node does provide a mechanism via native code, but I don't recommend its use - you're not supposed to know if something is a Proxy.

Other answers that suggest wrapping or shadowing the global Proxy will not actually work cross-realm (ie, iframes, web workers, node's vm module, wasm, etc).


u
user943702

There are two ways to proxy an object. One is new Proxy, another is Proxy.revocable. We may spy them so that proxied object are recorded to a secret list. Then we determine an object is a proxied object by checking if it exists in the secret list.

To spy functions, we may write wrappers or use the built-in Proxy. The latter means that use Proxy to proxy new Proxy as well as Proxy.recovable, here is a fiddle to demo the idea.

To serve the old Proxy API like nodejs-v5.8.0 Proxy, we may apply the same idea by using Proxy.createFunction to proxy Proxy.create and Proxy.createFunction.


This approach won't work for proxies created in other realms (like iframes, or node's vm modules), because you can't spy on globals (like Proxy) in other realms.
@LJHarb Are you sure? I actually tried that after reading your comment but had no problem in replacing the Proxy object across iframe realms. I didn't test that with vm modules but don't see why it shouldn't work the same way.
@GOTO0 what i mean is, unless you can run code first in that other realm, you can't guarantee it has a shadowed Proxy. For iframes specifically, I could just create a brand new one and get access to the original.
@LJHarb If so, it's a valid point and it equally applies to most other answers I see here. It's unfortunate that you chose the least visible answer to post your comment.
I posted an answer; feel free to vote it up if you find it valuable.
n
nick zoum

I believe I have found a safer way to check if the item is a proxy. This answer was inspired by 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
    });
}

Essentially what I've done is, instead of adding the __isProxy field to the target, I added this check: if (property === Symbol.for("__isProxy")) return true; in the getter of the proxy. This way if you are using a for-in loop or Object.keys or Object.hasOwnProperty, __isProxy will not exist.

Unfortunately, even though you can set the value of __isProxy, you will never be able to retrieve it, due the check on the getter. Therefore you should throw an error when the field gets set.

You could also use a Symbol to check whether a variable is a Proxy, if you think that its likely you want to use __isProxy as a different property.

Finally, I also added similar functionality for the target of the proxy, which can also be quite as hard to retrieve.