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

Michael Theriot michael.lee.theriot at gmail.com
Thu Mar 17 10:46:40 UTC 2016


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.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160317/a5579308/attachment-0001.html>


More information about the es-discuss mailing list