Tuesday 22 November 2016

Is it a Proxy

ES6 proxies try to be so transparent that they don't offer a way to know if one object is proxied or not. Proxies work in a way that applying the instanceof operator or accessing directly to the constructor property of a Proxy will make you think that it's the original object rather than an Proxy. I mean:

.
class Person{

}
var p1 = new Person();
var proxiedP1 = new Proxy(p1, {/* handler object here */};

console.log("proxiedP1 instanceof Proxy: " + (proxiedP1 instanceof Proxy)); //false
console.log("proxiedP1 instanceof Person: " + (proxiedP1 instanceof Person)); //true
console.log("proxiedP1.constructor: " + (proxiedP1.constructor.name)); //Person

So, what if for some reason you want to know if an object is a proxy or not? And what if you want to obtain the proxied object from your proxy? The simple solution I've come up with is including this functionality in your "get" trap. Write your get trap so that if you ask for a property like for example "_isProxy" or "_proxyTarget" they return the correct value. I mean, something like this.

proxyHandler = {
 get: function(target, propKey, receiver){
  switch (propKey){
    case "_isProxy":
     return true;
     break;
    case "_proxyTarget":
     return target;
     break;
    default:
     //your normal trap code here, for example
     console.log("intercepting...");
     return Reflect.get(target, propKey);
     break;
   }
  
  return Reflect.get(target, propKey);
 }

You should also modify your set trap to prevent _isProxy and _proxyTarget from being set. You can generalize the code to an extendProxyHandler function, like this:

function extendProxyHandler(handler){
 let originalGetTrap = handler.get;
 handler.get = function(target, propKey, receiver){
  switch (propKey){
   case "_isProxy":
    return true;
    break;
   case "_proxyTarget":
    return target;
    break;
   default:
    if (typeof handler.get === "function"){
     return originalGetTrap.call(this, target, propKey, receiver);
    }
    else{
     return Reflect.get(target, propKey);
    }
    break;
  }
 };
 
 let originalSetTrap = handler.set;
 handler.set = function(target, propKey, value, receiver){
  switch (propKey){
   case "_isProxy":
    break;
   case "_proxyTarget":
    break;
   default:
    if (typeof handler.set === "function"){
     return originalSetTrap.call(this, target, propKey, value, receiver);
    }
    else{
     return Reflect.set(target, propKey, value);
    }
    break;
  }
 };
}

I've put this code along with a sample here.

I'll also mention something that is a bit confusing. In the different proxy traps, "this" refers to the proxy. In the get and set traps we also have a "receiver" argument. This receiver is also a proxy, but somehow not the same one!? Let's see:

get: function(target, propKey, receiver){
  console.log("receiver inherits from Proxy: " + (target instanceof Proxy)); //true
  console.log("this inherits from Proxy: " + (this instanceof Proxy)); //true
  console.log("this === receiver: " + (this == receiver)); //false
  //log the method calls
  if (typeof target[propKey] === "function"){
   console.log("intercepting call to method: " + propKey ); 
  }

  return Reflect.get(target, propKey);
 }

No comments:

Post a Comment