tvcutsem / harmony-reflect

ES5 shim for ES6 Reflect and Proxy objects
http://www.ecma-international.org/ecma-262/6.0/#sec-reflection
Other
478 stars 48 forks source link

Object.create puzzle #33

Closed johnjbarton closed 10 years ago

johnjbarton commented 10 years ago

I'm trying to figure out what I am doing wrong. In the following code I create aProxy which only logs the get() call. Then I call Object.create(aProxy). I'm trying to puzzle out the resulting object's behavior and deal with it.

(function() {
    var obj = {};
    var handler = {
        get: function(target, name, receiver) {
            console.log('get ' + name);
            return target[name];
        }
    };
    var aProxy = Proxy(obj, handler);
    var created = Object.create(aProxy);
    console.log('created.__proto__', created.__proto__);
    console.log('created.__proto__ === aProxy ', created.__proto__ === aProxy);
    console.log('Object.getPrototypeOf(created) === aProxy ', Object.getPrototypeOf(created) === aProxy);
})();

When the code runs, the handler is called twice and we log an object, false and true:

get __proto__ testObjectCreated.html:9
created.__proto__ Object {} testObjectCreated.html:15
get __proto__ testObjectCreated.html:9
created.__proto__ === aProxy  false testObjectCreated.html:17
Object.getPrototypeOf(created) === aProxy  true testObjectCreated.html:18

This code acts as if created.__proto__ first calls [[Get]] on created, finds nothing in own properties, then calls [[Get]] on the first proto chain link, finds a proxy, triggers the .get trap, logs, then returns Object as the proto value of {} and logs it.

The subsequent lines show that created.__proto__ is not equivalent to Object.getPrototypeOf(created); the former is actually created.__proto__._proto__ while the latter is the correct created.__proto__.

Since created is not a proxy, to workaround this we need to discover in the get handler that we are a proxy for proto and return ourselves. Then we have to avoid getting the wrong result if we call aProxy.__proto__.

Have you encountered this and do you have any suggestions?

johnjbarton commented 10 years ago

Boris Zbarsky 1:32 PM (3 minutes ago)

to es-discuss On 5/5/14, 4:21 PM, John Barton wrote: Let me rephrase my question: why is the Proxy get even called in this case?

Because the way proto works is as if the JS implementation had done:

(function() {
  protoGetter = Object.getPrototypeOf;
  Object.defineProperty(Object.prototype, "__proto__",
    {
       set: function() { /* magic here */ },
       get: function() { return protoGetter(this); }
    });
})();

before any page script got to run.

Which means that __proto__ is a simple accessor property on Object.prototype. On the one hand, that means that by default it appears on all objects. On the other hand it means it can be shadowed, for example by someone doing an explicit defineProperty for that property name on some object.

But it can also be shadowed by proxies, because those get to intercept all property access. So when a obj.__proto__ get happens the implementation walks up the proto chain of "obj" looking for a proxy or an object with an own property named "__proto__". If a proxy is found, its get() is invoked and that's all there is to do as far as the implementation is concerned. If an own property named "__proto__" is found, then the implementation checks whether it's an accessor or value property, and either returns the value or calls the getter, depending on which sort it is.