Proxy vs Object.defineProperty precedence

Viewed 41

I found this rather odd, consider this snippet:

const data = {};

const prox = new Proxy(data, {
    get(target, key) { 
      console.log("proxy get triggered", key); 
      return target[key]; 
    },
    set(target, key, value) { 
      console.log("proxy set triggered", key, value); 
      return target[key] = value; 
    }
});

Object.defineProperty(prox, "my_prop", { 
  get() { 
    console.log("defineProperty get triggered");
    return window.my_prop; 
  }, 
  set(v) { 
    console.log("defineProperty.set triggered", v);
    return window.my_prop = v;
  }
});

console.log("setting prox.my_prop = 5");
prox.my_prop = 5;
console.log("set complete");
console.log("window.my_prop", window.my_prop);
console.log("data.my_prop", data.my_prop);

The result is confusing the hell out of me. Basically, it seems that by trying to define a property on the proxy, it instead defines it on the target of the proxy? Otherwise I don't see how it can trigger both setters and the value to end up where defineProperty wants to put it.

1 Answers

That's right. You cannot call Object.defineProperty on a Proxy object and affect the Proxy object itself (unless the proxy has a defineProperty trap). Otherwise, calling Object.defineProperty on the proxy will result in the property being defined on the target (not the proxy), as you see.

This behavior is specified here.

10.5.6 [[DefineOwnProperty]] ( P, Desc )
The [[DefineOwnProperty]] internal method of a Proxy exotic object O
takes arguments P (a property key) and Desc (a Property Descriptor)
and returns either a normal completion containing a Boolean
or a throw completion.

It performs the following steps when called:

1. Let handler be O.[[ProxyHandler]].
2. If handler is null, throw a TypeError exception.
3. Assert: Type(handler) is Object.
4. Let target be O.[[ProxyTarget]].
5. Let trap be ? GetMethod(handler, "defineProperty").
6. If trap is undefined, then
  a. Return ? target.[[DefineOwnProperty]](P, Desc).
7. ...

If the trap doesn't exist, the process results in a call of target.[[DefineOwnProperty]](P, Desc).

Another way of looking at it - a Proxy is not a normal object. For normal objects, it makes sense to talk about their own properties, and whether they're configurable, enumerable, setters, and so on. But a Proxy object is essentially only a wrapper with traps. You can change the wrapper by changing the traps, but it's only a wrapper, not a standard object - you can't give it properties like you would with a normal object.

Related