95

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?

user3840170
  • 26,597
  • 4
  • 30
  • 62
GOTO 0
  • 42,323
  • 22
  • 125
  • 158
  • 1
    Maybe proxy the Proxy? I mean proxy the Proxy function before any script involving Proxy.. – user943702 Apr 02 '16 at 16:24
  • 2
    Isn't the very purpose of proxies that you cannot distinguish them from a "normal" object? Why would you want to test this? – Bergi Apr 02 '16 at 16:42
  • 3
    @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. – GOTO 0 Apr 02 '16 at 16:49
  • 1
    I wrote a fiddle to clarify the idea,... https://jsfiddle.net/ycw7788/uauoxn7o/ – user943702 Apr 02 '16 at 17:06
  • @Bergi I would want to test it for debugging purposes. Eg some object is behaving in a way that makes no logical sense to me. Then suddenly I think: "wait, maybe this is a proxy". How do I then confirm that this hypothesis is correct? I also find reflection to generally have educational value, it lets me check my hypotheses in fairly straightforward ways (as opposed to only being able to test them by side effects) – Joshua Cheek Jul 24 '18 at 04:01
  • 1
    @JoshuaCheek For debugging, look at the object with a debugger. It will be capable of telling you whether it's a proxy or not. – Bergi Jul 24 '18 at 08:15
  • For those also using vue 3, trying to find out if you have a reactive object or not: https://stackoverflow.com/a/70805174/517914 – SimonSimCity Jul 14 '23 at 08:25

15 Answers15

39

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
Ricardo Stuven
  • 4,704
  • 2
  • 34
  • 36
Ally
  • 499
  • 4
  • 2
  • 1
    Question does not specify **node**. However, if you're using it, this is the best way. – Andrew Philips Feb 24 '23 at 01:23
  • This is the only correct positive answer free from loopholes. If you don’t mind the non-portability, this is the way to do it. That said, it’s probably still best not to treat proxies differently from other objects. – user3840170 Aug 18 '23 at 16:08
  • Correction: [@Oriol’s answer](/a/37974524/3840170) is also loophole-free and non-portable (not sure how up-to-date it is, though). – user3840170 Aug 20 '23 at 09:27
38

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.

Xabre
  • 1,292
  • 1
  • 11
  • 17
30

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]) ...
David Callanan
  • 5,601
  • 7
  • 63
  • 105
  • 5
    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 Jun 02 '20 at 19:31
  • @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). – David Callanan Jun 26 '21 at 17:04
  • I worked upon your idea to include the cases where the Proxy (and its get trap) is in library code (and out of the client's code reach) and added on-site testable example [here](https://stackoverflow.com/a/76496339/343721) – Jan Turoň Jun 17 '23 at 13:17
  • What’s stopping the user from writing `({ [isProxy]: true })`? – user3840170 Aug 15 '23 at 11:18
  • @user3840170 I suppose the idea is not to expose the `isProxy` symbol anywhere outside the piece of code that constructs the instance. – David Callanan Aug 25 '23 at 00:51
21

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
ADJenks
  • 2,973
  • 27
  • 38
20

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).

  • 15
    I wouldn't link an article that starts with a note "*this blog post is outdated*" :-) However, http://exploringjs.com/es6/ch_proxies.html states exactly the same – Bergi Apr 02 '16 at 16:44
  • 4
    @BrassApparatus Actually, it turns out that both his comment and my answer are wrong, as detailed [here](http://exploringjs.com/es6/ch_proxies.html#_pitfall-not-all-objects-can-be-wrapped-transparently-by-proxies). –  Dec 03 '16 at 16:37
  • @user663031 the answer is not wrong. Proxies might not be transparent to builtin methods, but they are to ordinary JavaScript, and it is not possible to determine from outside whether an object is a proxy object. – Bergi Jun 06 '23 at 20:37
11

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..

Vladislav Ihost
  • 2,127
  • 11
  • 26
  • 2
    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. – GOTO 0 Apr 04 '18 at 21:30
  • 2
    +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 Jun 15 '19 at 00:17
  • 1
    @Codesmith I found myself here after wondering why proxied events wouldn't dispatch. – ADJenks Jun 23 '20 at 21:29
  • "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) – David Callanan Jun 26 '21 at 17:08
  • @DavidCallanan It sounds like a "[leaky abstraction](https://en.wikipedia.org/wiki/Leaky_abstraction)" – ADJenks Oct 26 '21 at 17:09
  • @ADJenks Yes. Although I'd nearly argue the Proxy itself isn't a leaky abstraction, because it's abstracted as intended at the JavaScript level. It's the object cloning code which is leaking these details in its lower-level implementation which breaks through the Proxy. – David Callanan Oct 26 '21 at 17:25
11

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()));
Eylon Sultan
  • 936
  • 9
  • 16
  • It works! With this solution for browsers, and [this other solution](https://stackoverflow.com/a/50047404/2441655) for NodeJS, we have the two main contexts covered. :) – Venryx Oct 23 '20 at 00:02
  • 2
    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". – ADJenks Nov 25 '20 at 01:04
  • Doesn't work with `jsdom` or `jest` since they have a fake `postMessage()` function – Kevin Beal Jul 28 '21 at 21:58
  • 1
    This answer is not correct. Structured clone algorithm also throws DataCloneError for Function objects and DOM nodes. – ozanbora Jan 20 '22 at 08:22
  • 1
    Is there a reason `shouldBeCloneable({})` is false? It seems to me that you should check for `o?.constructor === ({}).constructor`... – Qwertie Apr 18 '22 at 23:53
  • An easier way to check whether an object is cloneable is to try calling `structuredClone(obj)` and catch the exception. – cayhorstmann Mar 13 '23 at 13:55
  • Is there a reason `shouldBeCloneable(Object.create(null))` is false? It seems to me that you should not check for `o?.constructor === ({}).constructor`... – user3840170 Jun 04 '23 at 12:36
  • Also, `isProxy` as defined here claims `[new Proxy({}, {})]` is a proxy, despite the array not *being* one, merely *containing* one. – user3840170 Aug 15 '23 at 11:26
  • Even simpler: `isProxy([location])` and `isProxy([new WeakMap])` return true. Neither even *contains* a proxy. – user3840170 Aug 15 '23 at 11:40
8

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.
    }
  • I don't understand this. `myProxySet.has()` will succeed if the object is a Proxy or not. – Matthew Dean Oct 31 '22 at 15:45
  • 1
    @MatthewDean: I think he means that you're responsible for ALWAYS placing proxies you create into the proxy set, not that the set has some ability to discern if the values it contains are proxies. I.e. if you are careful and keep track of all the proxies you generate you can check if an object is one that you proxied (but it won't help you with objects that some third party has proxied) – Mattia Jan 15 '23 at 16:28
5

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
Oriol
  • 274,082
  • 63
  • 437
  • 513
3

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.

bormat
  • 1,309
  • 12
  • 16
2

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 (i.e. between iframes, Web Workers, node's vm module, WebAssembly, etc.).

user3840170
  • 26,597
  • 4
  • 30
  • 62
LJHarb
  • 32,486
  • 2
  • 32
  • 38
  • 1
    You should support your claim *It is impossible to detect if something is a Proxy according to the JS language specification* by adding a reference to the actual JS language secification where it is written so - otherwise it is just your fantasy. – Jan Turoň May 30 '23 at 20:23
  • 1
    Sure, although it's trivially googleable: https://tc39.es/ecma262, but i'm a delegate on the JS language committee, and not being able to detect a Proxy is a critical motivation in its design, so I'm pretty sure it's not a fantasy, mine or otherwise. – LJHarb May 31 '23 at 22:29
  • Then please point in your answer the exact statement in the mentioned reference from which the "impossible to detect" is deduced and explain how it is compatible with the getPrototypeOf proxy trap (see the proposal in [my answer](https://stackoverflow.com/a/76368352/343721)) – Jan Turoň Jun 01 '23 at 06:38
  • 1
    It's not said explicitly anywhere in the spec - it's an emergent property of the design. You'll just have to accept that. Your answer doesn't at all solve this - certainly if the *creator* of the Proxy wishes to reveal that information it can do so, but the ask is to detect a Proxy you *did not* create, which is impossible by design (implicit is, without the cooperation of the Proxy creator). – LJHarb Jun 02 '23 at 16:49
  • Instead of enforcing "you'll just have to accept that" quote the spec (until you do it, it is only emergent property of your fantasy). I still find your claim inconsistent with the very existence of the getPrototypeOf trap (just like you claimed "it's trivially googleable" only to admit later "it's not said anywhere in the spec"). And to your objection: it is possible by creating a Proxy to the Proxy you did not create. – Jan Turoň Jun 03 '23 at 13:02
  • 1
    @JanTuroň It is not said anywhere in the ECMAScript specification that you cannot solve the halting problem. Therefore it is possible, and any claim to the contrary is just your fantasy. – user3840170 Jun 04 '23 at 12:11
  • 1
    @JanTuroň creating a proxy to another object can't tell you if that object is itself a proxy, that's the point (unless it intentionally leaks that information, of course). It isn't written in the spec because the spec doesn't contain design motivations, it contains normative requirements. I was an editor of the spec for 3 years, so if you won't take my word for it, you're welcome to continue believing whatever nonsense you like. – LJHarb Jun 04 '23 at 21:29
1

The has Proxy trap

Working on David Callanan's answer, I find the has Proxy trap more appropriate for this: extra testing each time proxy property is accessed causes immeasurable efficiency loss, which can add up if frequently called or having further extra logic in the get trap.

const DetectableProxy = obj => new Proxy(obj, {
  has(o, prop) {
    if(prop == Symbol.for("proxy")) return true;
    return prop in o;
  }
});

function isDetectableProxy(obj) {
  if(obj === null || typeof obj != "object") return false;
  return Symbol.for("proxy") in obj;
}

// -- EXAMPLE --

var obj = { foo: "bar" };
var sayObj = new Proxy(obj, { get: (o, prop) => "I say " + o[prop] });
var exposedSayObj = DetectableProxy(sayObj);

console.log(exposedSayObj.foo); // I say bar
console.log(isDetectableProxy(exposedSayObj)); // true
console.log(isDetectableProxy(sayObj)); // false
for(const prop in exposedSayObj) console.log(prop); // foo
Jan Turoň
  • 31,451
  • 23
  • 125
  • 169
0

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.

user943702
  • 956
  • 6
  • 12
  • 1
    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 Dec 23 '17 at 09:04
  • @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. – GOTO 0 Mar 11 '19 at 15:26
  • 1
    @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 Mar 11 '19 at 20:16
  • 1
    @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. – GOTO 0 Mar 11 '19 at 20:49
  • I posted an answer; feel free to vote it up if you find it valuable. – LJHarb Mar 12 '19 at 21:27
-1

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.

nick zoum
  • 7,216
  • 7
  • 36
  • 80
-1

The shortest form based on the answer of ADJenks:

var GlobalProxy = Proxy
Proxy = function Proxy(a,b) {
    if ((typeof this != "object") || !(this instanceof Proxy)) {
        return new Proxy(a,b)
    }
    var getLastPrototype = function(obj,parent){
        var proto = Object.getPrototypeOf(obj)
        if (proto !== null) {
            return getLastPrototype(proto,obj)
        }
        return parent?parent:obj
    }
    Object.setPrototypeOf(getLastPrototype(a),this)
    return new GlobalProxy(a,b)
}

With that it is possible to check if an Object is proxied using instanceof Proxy.

Here are some test cases:

class DevAbstr {
    devTest() {
        console.log('runned devTest without problems')
        return "SUCCESS"
    }
}
class DevObj extends DevAbstr {}
var test = Proxy(new DevObj,{
    set: function (t, k, v) {
        if (k === "IS_PROXY") throw "IS_PROXY is reserved"
        if (typeof t.set == "function") {
            t.set(k,v)
        } else {
            t[k] = v;
            console.log("original",t, k, v)
        }
        return true
    },
    get: function (t, k) {
        if (k === "IS_PROXY") return true
        if (k === "PROXIED_OBJECT") return t
        if (typeof t.get == "function") {
            return t.get(k)
        } else {
            return t[k]
        }
        return false
    }
})
console.log("test instanceof Proxy", test instanceof Proxy) // true
console.log("test instanceof DevAbstr", test instanceof DevAbstr) // true
console.log("test instanceof DevObj", test instanceof DevObj) // true
test.blubb = 123
console.log("test.IS_PROXY", test.IS_PROXY) // true
console.log("test",test) // Proxy(Object)
console.log("test.PROXIED_OBJECT",test.PROXIED_OBJECT) // source object
console.log("test.devTest()",test.devTest()) // works

;(function() {
    console.log("Proxy",Proxy)
})()

 // valid!
for (var k in test) {
    console.log(k+': ',test[k])
}

I also compiled this to ES5 without problems.

This approach is ugly, i knew, but it works quite well...

dkr8
  • 19
  • 3