Javascript Proxy set() local property on inherited objects

710 Views Asked by At

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

2

There are 2 best solutions below

2
On

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

2
On

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