Search code examples
javascriptecmascript-6es6-proxy

Assign a property above proto proxy


Following code contains proxy with get trap in object's __proto__. When getting some property from the object, according to js logic, trap is called only when object itself doesn't contain corresponding property. So after assignment the property appears in the object and get trap is not called. It workes exactly as I want.

var x = Object.create(new Proxy({}, {
  get(obj, key) {
    if (typeof key !== 'symbol') {
      console.log('Reading a nonexisting property: ' + key);
    }
  }
}));

var temp;
console.log(1, Object.prototype.hasOwnProperty.call(x, 'a'));

temp = x.a;
console.log(2, Object.prototype.hasOwnProperty.call(x, 'a'), temp);

temp = x.a;
console.log(3, Object.prototype.hasOwnProperty.call(x, 'a'), temp);

x.a = 12;
console.log(4, Object.prototype.hasOwnProperty.call(x, 'a'));

temp = x.a;
console.log(5, Object.prototype.hasOwnProperty.call(x, 'a'), temp);
.as-console-wrapper.as-console-wrapper { max-height: 100vh }

Now I'm adding a set trap:

var x = Object.create(new Proxy({}, {
  get(obj, key) {
    if (typeof key !== 'symbol') {
      console.log('Reading a nonexisting property: ' + key);
    }
  },
  set(obj, key, val, receiver) {
    console.log('Assigning a property: ' + key);
    Reflect.set(obj, key, val); // Inside of proxy, not outside
    //Reflect.set(receiver, key, val); // Infinite recursion
    return true;
  }
}));

var temp;
console.log(1, Object.prototype.hasOwnProperty.call(x, 'a'));

temp = x.a;
console.log(2, Object.prototype.hasOwnProperty.call(x, 'a'), temp);

temp = x.a;
console.log(3, Object.prototype.hasOwnProperty.call(x, 'a'), temp);

x.a = 12;
console.log(4, Object.prototype.hasOwnProperty.call(x, 'a'));

temp = x.a;
console.log(5, Object.prototype.hasOwnProperty.call(x, 'a'), temp);
.as-console-wrapper.as-console-wrapper { max-height: 100vh }

The problem is that I can't write a property directly to the object. The property is either written to the object which is wrapped by proxy instead of object that wraps the proxy or infinite recursion of set trap occurs.

So I want to get the same output as in the first snippet, but with new line Assigning a property: a.

PS: Same qusetion in Russian.


Solution

  • To actually create a property, you will have to use Object.defineProperty (or Reflect.defineProperty, if you prefer). Just setting the property through assignment or Reflect.set will indeed traverse the prototype chain, so when you do this to an object in a setter on its prototype (or a proxy trap), you always get recursion indeed.

    You can use

    new Proxy({}, {
      get(target, key) {
        if (typeof key !== 'symbol') {
          console.log('Reading a nonexisting property: ' + key);
        }
      },
      set(target, key, val, receiver) {
        console.log('Assigning a property: ' + key);
        return Reflect.defineProperty(receiver, key, {
          value: val,
          writable: true,
          enumerable: true,
          configurable: true
        });
      }
    });
    

    Full code:

    var x = Object.create(new Proxy({}, {
      get(obj, key) {
        if (typeof key !== 'symbol') {
          console.log('Reading a nonexisting property: ' + key);
        }
      },
      set(obj, key, val, receiver) {
        console.log('Assigning a nonexisting property: ' + key);
    
        return Reflect.defineProperty(receiver, key, {
          value: val,
          writable: true,
          enumerable: true,
          configurable: true
        });
      }
    }));
    
    var temp;
    console.log(1, Object.prototype.hasOwnProperty.call(x, 'a'));
    
    temp = x.a; // get trap
    console.log(2, Object.prototype.hasOwnProperty.call(x, 'a'), temp);
    
    temp = x.a; // get trap
    console.log(3, Object.prototype.hasOwnProperty.call(x, 'a'), temp);
    
    x.a = 12;   // set trap creates a property and sets it
    console.log(4, Object.prototype.hasOwnProperty.call(x, 'a'));
    
    temp = x.a; // direct read - no traps
    console.log(5, Object.prototype.hasOwnProperty.call(x, 'a'), temp);
    
    x.a = 42;   // direct write - no traps
    console.log(6, Object.prototype.hasOwnProperty.call(x, 'a'));
    
    temp = x.a; // direct read - no traps
    console.log(7, Object.prototype.hasOwnProperty.call(x, 'a'), temp);
    .as-console-wrapper.as-console-wrapper { max-height: 100vh }