Friday 7 July 2017

ES6 Proxies and getPrototypeOf

The other day, somehow came to my mind an old post about static methods in JavaScript, and I thought that similar to the "avoid overriding" reason that I mention in that post, another use case would be to prevent operations from being intercepted by a proxy. I immediately thought of key actions like getPrototypeOf as an action that is better to be sure that the real method is being called and not a replacement, so it's much better to have it as a static method than as an instance method. So to my surprise I found out that the Proxy API provides a trap for getPrototypeOf, that indeed intercepts all these operations:

  • Object.getPrototypeOf()
  • Reflect.getPrototypeOf()
  • __proto__
  • Object.prototype.isPrototypeOf()
  • instanceof

Seeing that list I thought, OK, it intercepts all those cases, but will it intercept the case when the runtime is traversing the prototype chain of an object?.

I've done a short test, and seems like the traversal of the prototype chain when the runtime searches for an item is not affected by the proxy trap.


class Person{
 constructor(name){
  this.name = name;
 }
 
 sayHi(){
  return "Hello I'm " + this.name;
 } 
}
 
let p1 = new Person("Francois");

let proxiedP1 = new Proxy(p1, {
  getPrototypeOf(target) {
 return null;
  }
});

console.log("- Operations with proxiedP1");

console.log("proxiedP1 instanceof Person: " + (proxiedP1 instanceof Person));
//false

proto = Reflect.getPrototypeOf(proxiedP1);
console.log("proto is " + (proto === null ? "NULL" : "NOT NULL"));
//proto is NULL


//INTERESTING, proxying getPrototypeOf does not have an effect in how the runtime itself gets access to the prototype chain, so the sayHi method continues to work, even if instanceof is now telling me that proxiedP1 is not a Person anymore
console.log(proxiedP1.sayHi());
//works fine

So if you run the code above you'll get that the internal prototype [[Prototype]] of proxiedP1 is null and that it is not an instance of Person, but the call to sayHi, that is not directly attached to the p1 object, but to its [[Prototype]], will work fine! So officially you are not a Person, but in fact you are...

I find the existance of this trap like a source of confusion, but well, you can read about some use case here.

This has raised some questions in my head. From the programmer perspective the access to a method in JavaScript is not based on Method Dispatch or Message Sending, it's just a property lookup. That property lookup just returns a function and then it's invoked (if it it does not exist the runtime will follow up the prototype chain to find it). I assumed that at the lower level it would be implemented like that, but this would means that the addition of proxies would have introduced a sort of conditional to any property access in any object in the system. The runtime would have to check if the object is a proxy and has a get trap to invoke that trap or just do the normal access. If this were this way, the introduction of proxies in ES6 engines would have had performance implications for any code (whether it uses proxies or not), so obviously there has to be much more to it.

No comments:

Post a Comment