Proxy handler.has() does not have a receiver argument?

Tom Van Cutsem tomvc.be at gmail.com
Thu Mar 17 23:29:48 UTC 2016


The rationale for not having a `receiver` argument to `has` is that the
value produced by the "in" operator is not normally dependent on the
receiver. This is in contrast with `get` and `set` which may find an
accessor up the proto chain that needs to run with a `this` bound to the
receiver.

That said, I follow your line of reasoning and it is true that `has`, `get`
and `set` are the three traps that can be called on a
proxy-used-as-prototype (now that `enumerate` is considered deprecated), so
it would be consistent to allow all of them to  refer back to the original
receiver. This enables the general pattern that you illustrate.

As you note, the weirdness of this is apparent because it doesn't normally
make sense to pass a `receiver` argument to Reflect.has(). However, if
`receiver` would be made visible in a Proxy handler's `has` trap, then
`Reflect.has` should nevertheless be likewise extended so that one can
faithfully forward the `receiver` argument.

Spec-wise, I think the only significant change is that 7.3.10 HasProperty
<http://www.ecma-international.org/ecma-262/6.0/#sec-hasproperty>, step 3
must be changed to `O.[[HasProperty]](P, O)` and all [[HasProperty]]
internal methods must likewise be extended with an extra argument (which
they ignore). Only the Proxy implementation in 9.5.7 would then actually
refer to that argument.

Cheers,
Tom

2016-03-17 11:46 GMT+01:00 Michael Theriot <michael.lee.theriot at gmail.com>:

> I feel like it should, or I am misunderstanding something fundamental. I
> made a basic scenario to explain:
>
> ```js
> var arrays = new WeakMap();
>
> function ArrayView(array) {
>   arrays.set(this, array);
>
>   return new Proxy(this, {
>     set: (target, property, value) => (arrays.has(this) && property in
> arrays.get(this))  ? arrays.get(this)[property] = value : target[property]
> = value,
>     get: (target, property)        => (arrays.has(this) && property in
> arrays.get(this))  ? arrays.get(this)[property]         : target[property],
>     has: (target, property)        => (arrays.has(this) && property in
> arrays.get(this)) || property in target
>   });
> }
>
> ArrayView.prototype = Object.create(Array.prototype, {
>   arrayLength: {
>     get() {
>       return arrays.get(this).length;
>     }
>   }
> });
> ```
>
> When `new ArrayView(somearray)` is called the reference to `somearray` is
> stored in the `arrays` weak map and a proxy is returned that allows you to
> manipulate indices on it, or fallback to the object for other properties.
>
> This could be simplified by putting the proxy on the prototype chain to
> reduce overhead and actually return a genuine `ArrayView` object instead:
>
> ```js
> var arrays = new WeakMap();
>
> function ArrayView2(array) {
>   arrays.set(this, array);
> }
>
> var protoLayer = Object.create(Array.prototype, {
>   arrayLength: {
>     get() {
>       return arrays.get(this).length;
>     }
>   }
> });
>
> ArrayView2.prototype = new Proxy(protoLayer, {
>   set: (target, property, value, receiver) => (arrays.has(receiver) &&
> property in arrays.get(receiver))  ? arrays.get(receiver)[property] = value
> : Reflect.set(target, property, value, receiver),
>   get: (target, property, receiver)        => (arrays.has(receiver) &&
> property in arrays.get(receiver))  ? arrays.get(receiver)[property]
> : Reflect.get(target, property, receiver),
>   has: (target, property)                  => (arrays.has(target)   &&
> property in arrays.get(target))   || Reflect.has(target, property)
> });
> ```
>
> Under this setup `target` refers to the protoLayer object which is useless
> here, but we can use the `receiver` argument in its place to access the
> weak map, and replace our set/get operations with Reflect.set/Reflect.get
> calls to the target (protoLayer) using a receiver (the instance) to pass
> the correct `this` value to the `arrayLength` getter and prevent infinite
> recursion.
>
> One problem - handler.has() lacks a receiver argument. So in this scenario
> when using the `in` operator it will always fail on array properties
> because we cannot check the weak map by passing in the instance.
>
> ```js
> var arr = [0, 1];
>
> var a = new ArrayView(arr);
> a.arrayLength; // 2
> 'arrayLength' in a; // true
> '0' in a; // true
> '1' in a; // true
> '2' in a; // false
>
> var b = new ArrayView2(arr);
> b.arrayLength; // 2
> 'arrayLength' in b; // true
> '0' in b; // false
> '1' in b; // false
> '2' in b; // false
> ```
>
> Without a receiver argument on handler.has(), it is practically useless
> for proxies used as a prototype. You can't reference the instance calling
> it and your target is simply the parent prototype.
>
> Is there a reason the handler.has() trap should not obtain the receiver
> when used on the prototype chain? I can understand why Reflect.has()
> wouldn't have a receiver argument (that wouldn't make sense) but this seems
> like a legitimate use for it. Otherwise I don't see a reason to use the
> handler.has() trap at all on prototype proxies except for bizarre behaviors
> that have nothing to do with the instance. It will always have the same
> behavior across all instances since you can't differentiate them.
>
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160318/c8daa581/attachment.html>


More information about the es-discuss mailing list