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?

25367 次浏览

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

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.

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.

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

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

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

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

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

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.

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.

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

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()));

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