4

According to MDN, handler.set() can trap Inherited property assignment:

Object.create(proxy)[foo] = bar;

In which case, how does one both monitor and allow local assignments on inherited objects?

var base = {
 foo: function(){
  return "foo";
 }
}

var proxy = new Proxy(base, {
 set: function(target, property, value, receiver){
  console.log("called: " + property + " = " + value, "on", receiver);
  //receiver[property] = value; //Infinite loop!?!?!?!?!
  //target[property] = value // This is incorrect -> it will set the property on base.
  
  /*
   Fill in code here.
  */
  return true;
 }
})

var inherited = {}
Object.setPrototypeOf(inherited, Object.create(proxy));

inherited.bar = function(){
 return "bar";
}

//Test cases
console.log(base.foo); //function foo
console.log(base.bar); //undefined
console.log(inherited.hasOwnProperty("bar")) //true
user3840170
  • 26,597
  • 4
  • 30
  • 62
blackening
  • 903
  • 6
  • 14

2 Answers2

6

After some additional thought, i noticed that it intercepts 3 ops:

Property assignment: proxy[foo] = bar and proxy.foo = bar Inherited property assignment: Object.create(proxy)[foo] = bar
Reflect.set()

but not Object.defineProperty() which appears to be even lower level than the = operator.

Thus the following works:

var base = {
    foo: function(){
        return "foo";
    }
};

var proxy = new Proxy(base, {
    set: function(target, property, value, receiver){
        var p = Object.getPrototypeOf(receiver);
      
        Object.defineProperty(receiver, property, { value: value });   // ***
        return true;
    }
});

var inherited = {};
Object.setPrototypeOf(inherited, Object.create(proxy));

inherited.bar = function(){
    return "bar";
};

// Test cases
console.log(base.foo);                       // function foo
console.log(base.bar);                       // undefined
console.log(inherited.bar);                  // function bar
console.log(inherited.hasOwnProperty("bar")) // true
blackening
  • 903
  • 6
  • 14
  • 1
    Why didn't I think of that?! :-) You probably want `writable: true, enumerable: true, configurable: true` in there, to make it equivalent. – T.J. Crowder Jan 09 '17 at 08:34
  • That comment of yours just helped me find a bug. Didn't know that enumerable (and all other properties) is false by default X.X – blackening Jan 09 '17 at 09:03
1

I see two options (maybe):

  1. Store the property in a Map, keeping the Maps for various receivers in a WeakMap keyed by the receiver. Satisfy get by checking the Map and returning the mapping there instead of from the object. (Also has.) Slight problem is that you also need to proxy the receiver (not just base) in order to handle ownKeys. So this could be unworkable.

  2. Temporarily get the proxy out of the inheritance chain while setting.

Here's that second one:

var base = {
    foo: function(){
        return "foo";
    }
};

var proxy = new Proxy(base, {
    set: function(target, property, value, receiver){
        const p = Object.getPrototypeOf(receiver); // ***
        Object.setPrototypeOf(receiver, null);     // ***
        receiver[property] = value;                // ***
        Object.setPrototypeOf(receiver, p);        // ***
        return true;
    }
});

var inherited = {};
Object.setPrototypeOf(inherited, Object.create(proxy));

inherited.bar = function(){
    return "bar";
};

// Test cases
console.log("base.foo:", base.foo); // function foo
console.log("base.bar:", base.bar); // undefined
console.log("inherited.bar:", inherited.bar); // function bar
console.log("inherited has own bar?", inherited.hasOwnProperty("bar")); // true
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Sorry, i thought of another answer using `Object.defineProperty()`. If there weren't that loophole in what the proxy catches, i think your answer would have been the best. – blackening Jan 09 '17 at 08:31
  • @blackening: Without doubt, yours is the better way. – T.J. Crowder Jan 09 '17 at 09:07