Related
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?
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.
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]) ...
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
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).
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..
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()));
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.
}
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.
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
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).
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.
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.
This question already has answers here:
Why is Proxy to a Map object in ES2015 not working
(2 answers)
Closed 5 years ago.
I use a Map javascript object. I'd like to observe changes to a Map instance using a Proxy object. However, whatever handler object I try to feed in to the proxy, I consistently get the error Method Map.prototype.set called on incompatible receiver. What am I doing wrong? The idiomatic example I expect to work looks like
var map = new Map();
var proxy = new Proxy(map, {
set(obj, prop, value) {
console.log(value);
}
});
proxy.set(1, 1); --> error
I also tried setting a handler for apply, but to no avail.
First off, understand that your error also reproduces with just
var map = new Map();
var proxy = new Proxy(map, {});
proxy.set(1, 1);
It is not related to your usage of set(obj, prop, value).
Why it fails
To break that a bit more, understand that this is basically the same as doing
var map = new Map();
var proxy = new Proxy(map, {});
Map.prototype.set.call(proxy, 1, 1);
which also errors. You are calling the set function for Map instances, but passing it a Proxy instead of a Map instance.
And that is the core of the issue here. Maps store their data using an private internal slot that is specifically associated with the map object itself. Proxies do not behave 100% transparently. They allow you to intercept a certain set of operations on an object, and perform logic when they happen, which usually means proxying that logic through to some other object, in your case from proxy to map.
Unfortunately for your case, proxying access to the Map instance's private internal slot is not one of the behaviors that can be intercepted. You could kind of imagine it like
var PRIVATE = new WeakMap();
var obj = {};
PRIVATE.set(obj, "private stuff");
var proxy = new Proxy(obj, {});
PRIVATE.get(proxy) === undefined // true
PRIVATE.get(obj) === "private stuff" // true
so because the object pass as this to Map.prototype.set is not a real Map, it can't find the data it needs and will throw an exception.
Solution
The solution here means you actually need to make the correct this get passed to Map.prototype.set.
In the case of a proxy, the easiest approach would be to actually intercept the access to .set, e.g
var map = new Map();
var proxy = new Proxy(map, {
get(target, prop, receiver) {
// Perform the normal non-proxied behavior.
var value = Reflect.get(target, prop, receiver);
// If something is accessing the property `proxy.set`, override it
// to automatically do `proxy.set.bind(map)` so that when the
// function is called `this` will be `map` instead of `proxy`.
if (prop === "set" && typeof value === "function") value = value.bind(target);
return value;
}
});
proxy.set(1, 1);
Of course that doesn't address your question about intercepting the actual calls to .set, so for that you can expand on this to do
var map = new Map();
var proxy = new Proxy(map, {
get(target, prop, receiver) {
var value = Reflect.get(target, prop, receiver);
if (prop === "set" && typeof value === "function") {
// When `proxy.set` is accessed, return your own
// fake implementation that logs the arguments, then
// calls the original .set() behavior.
const origSet = value;
value = function(key, value) {
console.log(key, value);
return origSet.apply(map, arguments);
};
}
return value;
}
});
proxy.set(1, 1);
I'm running the following script through Google Chrome Version 57.0.2987.133:
var loggingProxyHandler = {
"get" : function(targetObj, propName, receiverProxy) {
let ret = Reflect.get(targetObj, propName, receiverProxy);
console.log("get("+propName.toString()+"="+ret+")");
return ret;
},
"set" : function(targetObj, propName, propValue, receiverProxy) {
console.log("set("+propName.toString()+"="+propValue+")");
return Reflect.set(targetObj, propName, propValue, receiverProxy);
}
};
function onRunTest()
{
let m1 = new Map();
let p1 = new Proxy(m1, loggingProxyHandler);
p1.set("a", "aval"); // Exception thrown from here
}
onRunTest();
NOTE: Requires a browser supporting ES2015's Proxy
When run, I see the handler's get trap is called to return the Map's set function
and then I receive the following error:
"Uncaught TypeError: Method Map.prototype.set called on incompatible receiver [object Object]"
at Proxy.set (native)
...
I tried removing the trap functions from the loggingProxyHandler (making it an empty object) but still receive the same error.
My understanding was that a Proxy object was supposed to be able to generated for all native ES5 and ES2015 javascript objects. Array seems to work well under the same proxy handler.
Did I misunderstand the specs?
Is my code missing something?
Is there a known bug in Chrome? (I did a search and found no defects for Chrome on this subject.)
The reason you're getting the error is that the proxy isn't getting involved in the p1.set() method call (other than that the get trap is used to retrieve the function reference). So once the function reference has been retrieved, it's called with this set to the proxy p1, not the map m1 — which the set method of a Map doesn't like.
If you're really trying to intercept all property access calls on the Map, you can fix it by binding any function references you're returning from get (see the *** lines):
const loggingProxyHandler = {
get(target, name/*, receiver*/) {
let ret = Reflect.get(target, name);
console.log(`get(${name}=${ret})`);
if (typeof ret === "function") { // ***
ret = ret.bind(target); // ***
} // ***
return ret;
},
set(target, name, value/*, receiver*/) {
console.log(`set(${name}=${value})`);
return Reflect.set(target, name, value);
}
};
function onRunTest() {
const m1 = new Map();
const p1 = new Proxy(m1, loggingProxyHandler);
p1.set("a", "aval");
console.log(p1.get("a")); // "aval"
console.log(p1.size); // 1
}
onRunTest();
NOTE: Requires a browser supporting ES2015's Proxy
Notice that when calling Reflect.get and Reflect.set, we don't pass along the receiver (in fact, we're not using the receiver argument at all in those, so I've commented the parameter out). That means they'll use the target itself as the receiver, which you need if the properties are accessors (like Map's size property) and they need their this to be the actual instance (as Map's size does).
If your goal is just to intercept Map#get and Map#set, though, you don't need a proxy at all. Either:
Create a Map subclass and instantiate that. Assumes you control the creation of the Map instance, though.
Create a new object that inherits from the Map instance, and override get and set; you don't have to be in control of the original Map's creation.
Replace the set and get methods on the Map instance with your own versions.
Here's #1:
class MyMap extends Map {
set(...args) {
console.log("set called");
return super.set(...args);
}
get(...args) {
console.log("get called");
return super.get(...args);
}
}
const m1 = new MyMap();
m1.set("a", "aval");
console.log(m1.get("a"));
#2:
const m1 = new Map();
const p1 = Object.create(m1, {
set: {
value: function(...args) {
console.log("set called");
return m1.set(...args);
}
},
get: {
value: function(...args) {
console.log("get called");
return m1.get(...args);
}
}
});
p1.set("a", "aval");
console.log(p1.get("a"));
#3:
const m1 = new Map();
const m1set = m1.set; // Yes, we know these are `Map.prototype.set` and
const m1get = m1.get; // `get`, but in the generic case, we don't necessarily
m1.set = function(...args) {
console.log("set called");
return m1set.apply(m1, args);
};
m1.get = function(...args) {
console.log("get called");
return m1get.apply(m1, args);
}
m1.set("a", "aval");
console.log(m1.get("a"));
Let me add more to this.
Many built-in objects, for example Map, Set, Date, Promise and others make use of so-called internal slots.
These are like properties but reserved for internal, specification-only purposes. For instance, Map stores items in the internal slot [[MapData]]. Built-in methods access them directly, not via [[Get]]/[[Set]] internal methods. So Proxy can’t intercept that.
For example:
let map = new Map();
let proxy = new Proxy(map, {});
proxy.set('name', 'Pravin'); // Error
Internally, a Map stores all data in its [[MapData]] internal slot. The proxy doesn't have such slot. The built-in method Map.prototype.set method tries to access the internal property this.[[MapData]], but because this=proxy, can't find it in proxy and just fails.
There’s a way to fix it:
let map = new Map();
let proxy = new Proxy(map,{
get(target,prop,receiver){
let value = Reflect.get(...arguments);
return typeof value === 'function'?value.bind(target):value;
}
});
proxy.set('name','Pravin');
console.log(proxy.get('name')); //Pravin (works!)
Now it works fine, because get trap binds function properties, such as map.set, to the target object (map) itself. So the value of this inside proxy.set(...) will be not proxy, but the original map. So when the internal implementation of set tries to access this.[[MapData]] internal slot, it succeeds.
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?
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.
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]) ...
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
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).
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..
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()));
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.
}
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.
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
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).
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.
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.
There is a noSuchMethod feature in some javascript implementations (Rhino, SpiderMonkey)
proxy = {
__noSuchMethod__: function(methodName, args){
return "The " + methodName + " method isn't implemented yet. HINT: I accept cash and beer bribes" ;
},
realMethod: function(){
return "implemented" ;
}
}
js> proxy.realMethod()
implemented
js> proxy.newIPod()
The newIPod method isn't implemented yet. HINT: I accept cash and beer bribes
js>
I was wondering, is there was a way to do something similar for properties? I'd like to write proxy classes that can dispatch on properties as well as methods.
UPDATE: ECMAScript 6 Proxies are widely supported now. Basically, if you don't need to support IE11, you can use them.
Proxy objects allow you to define custom behavior for fundamental operations, like property lookup, assignment, enumeration, function invocation, etc.
Emulating __noSuchMethod__ with ES6 Proxies
By implementing traps on property access, you can emulate the behavior of the non-standard __noSuchMethod__ trap:
function enableNoSuchMethod(obj) {
return new Proxy(obj, {
get(target, p) {
if (p in target) {
return target[p];
} else if (typeof target.__noSuchMethod__ == "function") {
return function(...args) {
return target.__noSuchMethod__.call(target, p, args);
};
}
}
});
}
// Example usage:
function Dummy() {
this.ownProp1 = "value1";
return enableNoSuchMethod(this);
}
Dummy.prototype.test = function() {
console.log("Test called");
};
Dummy.prototype.__noSuchMethod__ = function(name, args) {
console.log(`No such method ${name} called with ${args}`);
return;
};
var instance = new Dummy();
console.log(instance.ownProp1);
instance.test();
instance.someName(1, 2);
instance.xyz(3, 4);
instance.doesNotExist("a", "b");
Original 2010 answer
There is only one existing thing at the moment that can actually do what you want, but unfortunately is not widely implemented:
ECMAScript Harmony Proxies.
There are only two working implementations available at this time, in the latest Firefox 4 betas (it has been around since FF3.7 pre-releases) and in node-proxy for server-side JavaScript -Chrome and Safari are currently working on it-.
It is one of the early proposals for the next version of ECMAScript, it's an API that allows you to implement virtualized objects (proxies), where you can assign a variety of traps -callbacks- that are executed in different situations, you gain full control on what at this time -in ECMAScript 3/5- only host objects could do.
To build a proxy object, you have to use the Proxy.create method, since you are interested in the set and get traps, I leave you a really simple example:
var p = Proxy.create({
get: function(proxy, name) { // intercepts property access
return 'Hello, '+ name;
},
set: function(proxy, name, value) { // intercepts property assignments
alert(name +'='+ value);
return true;
}
});
alert(p.world); // alerts 'Hello, world'
p.foo = 'bar'; // alerts foo=bar
Try it out here.
EDIT: The proxy API evolved, the Proxy.create method was removed in favor of using the Proxy constructor, see the above code updated to ES6:
const obj = {};
const p = new Proxy(obj, {
get(target, prop) { // intercepts property access
return 'Hello, '+ prop;
},
set(target, prop, value, receiver) { // intercepts property assignments
console.log(prop +'='+ value);
Reflect.set(target, prop, value, receiver)
return true;
}
});
console.log(p.world);
p.foo = 'bar';
The Proxy API is so new that isn't even documented on the Mozilla Developer Center, but as I said, a working implementation has been included since the Firefox 3.7 pre-releases.
The Proxy object is available in the global scope and the create method can take two arguments, a handler object, which is simply an object that contains properties named as the traps you want to implement, and an optional proto argument, that makes you able to specify an object that your proxy inherits from.
The traps available are:
// TrapName(args) Triggered by
// Fundamental traps
getOwnPropertyDescriptor(name): // Object.getOwnPropertyDescriptor(proxy, name)
getPropertyDescriptor(name): // Object.getPropertyDescriptor(proxy, name) [currently inexistent in ES5]
defineProperty(name, propertyDescriptor): // Object.defineProperty(proxy,name,pd)
getOwnPropertyNames(): // Object.getOwnPropertyNames(proxy)
getPropertyNames(): // Object.getPropertyNames(proxy)
delete(name): // delete proxy.name
enumerate(): // for (name in proxy)
fix(): // Object.{freeze|seal|preventExtensions}(proxy)
// Derived traps
has(name): // name in proxy
hasOwn(name): // ({}).hasOwnProperty.call(proxy, name)
get(receiver, name): // receiver.name
set(receiver, name, val): // receiver.name = val
keys(): // Object.keys(proxy)
The only resource I've seen, besides the proposal by itself, is the following tutorial:
Harmony Proxies: Tutorial
Edit: More information is coming out, Brendan Eich recently gave a talk at the JSConf.eu Conference, you can find his slides here:
Proxies are Awesome!
Here's how to get behaviour similar to __noSuchMethod__
First of all, here's a simple object with one method:
var myObject = {
existingMethod: function (param) {
console.log('existing method was called', param);
}
}
Now create a Proxy which will catch access to properties/method and add your existing object as a first parameter.
var myObjectProxy = new Proxy(myObject, {
get: function (func, name) {
// if property or method exists, return it
if( name in myObject ) {
return myObject[name];
}
// if it doesn't exists handle non-existing name however you choose
return function (args) {
console.log(name, args);
}
}
});
Now try it:
myObjectProxy.existingMethod('was called here');
myObjectProxy.nonExistingMethod('with a parameter');
Works in Chrome/Firefox/Opera. Doesn't work in IE(but already works in Edge). Also tested on mobile Chrome.
Creation of proxy can be automated and invisible i.e. if you use Factory pattern to build your objects. I did that to create workers which internal functions can be called directly from the main thread. Using workers can be now so simple thanks to this cool new feature called Proxy. The simplest worker implementation ever:
var testWorker = createWorker('pathTo/testWorker.js');
testWorker.aFunctionInsideWorker(params, function (result) {
console.log('results from worker: ', result);
});
I don't believe this type of metaprogramming is possible (yet) in javascript. Instead, try using the __noSuchMethod__ functionality to achieve the effect with property getters. Not cross-browser as it's a Mozilla extension.
var proxy = {
__noSuchMethod__: function(methodName, args) {
if(methodName.substr(0,3)=="get") {
var property = methodName.substr(3).toLowerCase();
if (property in this) {
return this[property];
}
}
}, color: "red"
};
alert(proxy.getColor());
You can use the Proxy class.
var myObj = {
someAttr: 'foo'
};
var p = new Proxy(myObj, {
get: function (target, propName) {
// target is the first argument passed into new Proxy,
// in this case target === myObj
return 'myObj with someAttr:"' + target.someAttr
+ '" had "' + propName
+ '" called on it.';
}
});
console.log(p.nonExsistantProperty);
// outputs:
// myObj with someAttr:"foo" had "nonExsistantProperty" called on it
There is __defineGetter__, __defineSetter__, __lookupGetter__ and __lookupSetter__ in addition to __noSuchMethod__ in SpiderMonkey.
Although this is an old question I was looking into this today. I wanted to be able to seamlessly integrate code from another context, maybe a different web page or server.
Its the sort of thing that breaks in the long run, but I think its an interesting concept none the less. These things can be useful for mashing code together quickly, ( which then exists for years, buried somewhere ).
var mod = modproxy();
mod.callme.first.now('hello', 'world');
mod.hello.world.plot = 555;
var v = mod.peter.piper.lucky.john.valueOf;
console.log(v);
mod.hello.world = function(v) {
alert(v);
return 777;
};
var v = mod.hello.world('funky...');
console.log(v);
var v = mod.hello.world.plot.valueOf;
console.log(v);
mod.www.a(99);
mod.www.b(98);
function modproxy(__notfound__) {
var mem = {};
return newproxy();
function getter(target, name, receiver, lname) {
if(name === 'valueOf') {
lname=lname.slice(1);
if(lname in mem) {
var v = mem[lname];
console.log(`rd : ${lname} - ${v}`);
return v;
}
console.log(`rd (not found) : ${lname}`);
return;
}
lname += '.'+name;
return newproxy(() => {}, lname);
} // getter
function setter(obj, prop, newval, lname) {
lname += '.' + prop;
lname = lname.slice(1);
console.log(`wt : ${lname} - ${newval}`);
mem[lname] = newval;
} // setter
function applyer(target, thisArg, args, lname) {
lname = lname.slice(1);
if(lname in mem) {
var v = mem[lname];
if(typeof v === 'function') {
console.log(`fn : ${lname} - [${args}]`);
return v.apply(thisArg,args);
}
return v;
}
console.log(`fn (not found): ${lname} - [${args}]`);
} // applyer
function newproxy(target, lname) {
target = target || {};
lname = lname || '';
return new Proxy(target, {
get: (target, name, receiver) => {
return getter(target, name, receiver, lname);
},
set: (target, name, newval) => {
return setter(target, name, newval, lname);
},
apply: (target, thisArg, args) => {
return applyer(target, thisArg, args, lname);
}
});
} //proxy
} //modproxy
I implemented a valueOf step to read values, because it seems the property is 'get-ted' first.
I dont think its possible to tell at the time the property is 'get-ted' if its going to be invoked or read ( or required for further chaining ).
Its ok for single level properties. The property is either there or its not and either the required type or its not.
I'll work on it further, looking at promises for async/await routines, proxying existing objects and finer control of how properties are accessed when I am more familiar with how the code behaves and is best implemented.
I created a repository on GitHub: modproxy.js/README.md
CodePen: modproxy.js
My original question:
does javascript have an equivalent to the php magic class __call