michael.lee.theriot at gmail.com (2016-03-18T14:12:41.302Z)
I'm trying to make the proxy-as-a-prototype pattern work but I've just discovered the `ownKeys` trap is never called on proxies on the prototype. So even if the `has` trap is allowed to see the `receiver`, and thus verify the properties "0", "1" exist, this pattern would fail to return the properties "0", "1" exist on an `Object.getOwnPropertyNames` call. Disappointing! I'd rather use a proxy on the prototype than create one for each instance but without a correct `ownKeys` return it just doesn't come full circle. Is there a trick to make this work or am I out of luck here? I can only think of actually defining the properties to make it work, which defeats the idea of using a proxy on the prototype to begin with. Regardless I agree that traps called on a prototype chain should always receive the `receiver` as an argument. I think the only trap other than `set`, `get`, and `has` that can do this is the `getPrototypeOf` trap (currently does not have a `receiver`) when the `instanceof` check needs to climb the prototype chain.
I'm trying to make the proxy-as-a-prototype pattern work but I've just discovered the `ownKeys` trap is never called on traps on the prototype. So even if the `has` trap is allowed to see the `receiver`, and thus verify the properties "0", "1" exist, this pattern would fail to return the properties "0", "1" exist on an `Object.getOwnPropertyNames` call. Disappointing! I'd rather use a proxy on the prototype than create one for each instance but without a correct `ownKeys` return it just doesn't come full circle. Is there a trick to make this work or am I out of luck here? I can only think of actually defining the properties to make it work, which defeats the idea of using a proxy on the prototype to begin with. Regardless I agree that traps called on a prototype chain should always receive the `receiver` as an argument. I think the only trap other than `set`, `get`, and `has` that can do this is the `getPrototypeOf` trap (currently does not have a `receiver`) when the `instanceof` check needs to climb the prototype chain. On Thu, Mar 17, 2016 at 6:29 PM, Tom Van Cutsem <tomvc.be at gmail.com> wrote: > 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/d40f1134/attachment.html>