Proxy handler.has() does not have a receiver argument?
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 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>:
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>
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>
Agreed with everybody else the receiver
is always needed and Proxy
on
the prototype makes way more sense than per instance.
Also the getPrototypeOf
trap is really pointless right now
function Yak() {}
Yak.prototype = new Proxy(Yak.prototype, {
getPrototypeOf: (target) => console.log('lulz')
});
var yup = new Yak;
Object.getPrototypeOf(yup);
The target
is actually the original Yak.prototype
which is already the
yup
prototype: useless trap if used in such way.
Being also unable to distinguish between getOwnPropertyNames
vs keys
is
a bit weird.
Proxy
looks so close to be that powerful but these bits make it kinda
useless for most real-world cases I've been recently dealing with.
Thanks for any sort of improvement.
Agreed with everybody else the `receiver` is always needed and `Proxy` on the prototype makes way more sense than per instance. Also the `getPrototypeOf` trap is really pointless right now ```js function Yak() {} Yak.prototype = new Proxy(Yak.prototype, { getPrototypeOf: (target) => console.log('lulz') }); var yup = new Yak; Object.getPrototypeOf(yup); ``` The `target` is actually the original `Yak.prototype` which is already the `yup` prototype: useless trap if used in such way. Being also unable to distinguish between `getOwnPropertyNames` vs `keys` is a bit weird. `Proxy` looks so close to be that powerful but these bits make it kinda useless for most real-world cases I've been recently dealing with. Thanks for any sort of improvement. Regards On Fri, Mar 18, 2016 at 1:54 PM, Michael Theriot < michael.lee.theriot at gmail.com> wrote: > 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 >>> >>> >> > > _______________________________________________ > 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/c2361eb0/attachment-0001.html>
On Mar 18, 2016, at 9:24 AM, Andrea Giammarchi <andrea.giammarchi at gmail.com> wrote:
Agreed with everybody else the
receiver
is always needed andProxy
on the prototype makes way more sense than per instance.
I don’t agree. While you certainly can imagine a language where each object’s “prototype” determines that object’s fundamental behaviors and provides the MOP intercession hooks(in fact that’s how most class-based languages work). But that’s not the JS object model. Each JS object is essentially a singleton that defines it’s own fundamental behaviors. Whether or this model is better or worse than the class-based model isn't really relevant, but in the context of JS there are advantage to consistently adhering to that model,
For example, in Michael’s desired approach, the instance objects of his ArrayView abstraction are “ordinary objects”. One of the fundamental behavioral characteristics of ordinary objects is that all of there own properties are defined and available to the implementation in a standard way. Implementations certainly make use of that characteristic for optimization purposes. Michael’s approach would make such optimizations invalid because every time an own property needed to be access a prototype walk would have to be performed because there might be an exotic object somewhere on the prototype chain that was injecting own property into the original “receiver”.
Michael’s preferred approach also introduces observable irregularity into the standard JS inheritance model for ordinary objects.
Consider an object created using Michael’s preferred approach:
var arr = [0, 1];
console.log(Reflect.has(arr,”0”)); //true, arr has “0” as an own property
var subArr = Object.create(arr);
console.log(Reflect.has(subArr,”0”)); //true, all unshadowed properties of proto visible from ordinary objects
var b = new ArrayView2(arr);
console.log(Reflect.has(b,”0”)); //true, prototype proxy makes array elements visible as if properties of b
var subB= Object.create(b);
console.log(Reflect.has(subB,”0”)); //false, some unshadowed properties of proto is not visible from subB
Note the his original Proxy implementation does not have this undesirable characteristic.
So what about the use of receiver
in [[Get]]/[[Set]]. That’s a different situation. [[Get]]/[[Set]] are not fundamental, rather they are derived (they work by applying other more fundamental MOP operations). The receiver
argument is not used by them to perform property lookup (they use [[GetOwnProperty]] and [[GetPrototypeOf]]) for the actual property lookup). receiver
is only used in the semantics of what happens after the property lookup occurs. Adding a receiver
argument to the other MOP operations for the purpose of changing property lookup semantics seems like a step too far. The ES MOP design is a balancing act between capability, implementability, and consistency. I think adding receiver
to every MOP operation would throw the design out of balance.
Finally,
Note that we are not really talking about a new capability here. Michael’s first design shows that ES proxies already have the capability to implement the object level semantics he desires. So, we are only talking about exactly how he goes about using Proxy to implement that semantics. He would prefer a different Proxy design than what was actually provided by ES6. But that isn’t what was specified or what has now been implemented. We can all imagine how many JS features might be “better” if they worked somewhat differently. But that generally isn’t an option. The existing language features and their implementations are what they are and as JS programmers we need to work within that reality.
> On Mar 18, 2016, at 9:24 AM, Andrea Giammarchi <andrea.giammarchi at gmail.com> wrote: > > Agreed with everybody else the `receiver` is always needed and `Proxy` on the prototype makes way more sense than per instance. I don’t agree. While you certainly can imagine a language where each object’s “prototype” determines that object’s fundamental behaviors and provides the MOP intercession hooks(in fact that’s how most class-based languages work). But that’s not the JS object model. Each JS object is essentially a singleton that defines it’s own fundamental behaviors. Whether or this model is better or worse than the class-based model isn't really relevant, but in the context of JS there are advantage to consistently adhering to that model, For example, in Michael’s desired approach, the instance objects of his ArrayView abstraction are “ordinary objects”. One of the fundamental behavioral characteristics of ordinary objects is that all of there own properties are defined and available to the implementation in a standard way. Implementations certainly make use of that characteristic for optimization purposes. Michael’s approach would make such optimizations invalid because every time an own property needed to be access a prototype walk would have to be performed because there might be an exotic object somewhere on the prototype chain that was injecting own property into the original “receiver”. Michael’s preferred approach also introduces observable irregularity into the standard JS inheritance model for ordinary objects. Consider an object created using Michael’s preferred approach: ```js var arr = [0, 1]; console.log(Reflect.has(arr,”0”)); //true, arr has “0” as an own property var subArr = Object.create(arr); console.log(Reflect.has(subArr,”0”)); //true, all unshadowed properties of proto visible from ordinary objects var b = new ArrayView2(arr); console.log(Reflect.has(b,”0”)); //true, prototype proxy makes array elements visible as if properties of b var subB= Object.create(b); console.log(Reflect.has(subB,”0”)); //false, some unshadowed properties of proto is not visible from subB ``` Note the his original Proxy implementation does not have this undesirable characteristic. So what about the use of `receiver` in [[Get]]/[[Set]]. That’s a different situation. [[Get]]/[[Set]] are not fundamental, rather they are derived (they work by applying other more fundamental MOP operations). The `receiver` argument is not used by them to perform property lookup (they use [[GetOwnProperty]] and [[GetPrototypeOf]]) for the actual property lookup). `receiver` is only used in the semantics of what happens after the property lookup occurs. Adding a `receiver` argument to the other MOP operations for the purpose of changing property lookup semantics seems like a step too far. The ES MOP design is a balancing act between capability, implementability, and consistency. I think adding `receiver` to every MOP operation would throw the design out of balance. Finally, Note that we are not really talking about a new capability here. Michael’s first design shows that ES proxies already have the capability to implement the object level semantics he desires. So, we are only talking about exactly how he goes about using Proxy to implement that semantics. He would prefer a different Proxy design than what was actually provided by ES6. But that isn’t what was specified or what has now been implemented. We can all imagine how many JS features might be “better” if they worked somewhat differently. But that generally isn’t an option. The existing language features and their implementations are what they are and as JS programmers we need to work within that reality. Allen > > Also the `getPrototypeOf` trap is really pointless right now > > ```js > function Yak() {} > Yak.prototype = new Proxy(Yak.prototype, { > getPrototypeOf: (target) => console.log('lulz') > }); > > var yup = new Yak; > Object.getPrototypeOf(yup); > ``` > > The `target` is actually the original `Yak.prototype` which is already the `yup` prototype: useless trap if used in such way. > > Being also unable to distinguish between `getOwnPropertyNames` vs `keys` is a bit weird. > > `Proxy` looks so close to be that powerful but these bits make it kinda useless for most real-world cases I've been recently dealing with. > > Thanks for any sort of improvement. > > Regards > > > > > On Fri, Mar 18, 2016 at 1:54 PM, Michael Theriot <michael.lee.theriot at gmail.com <mailto:michael.lee.theriot at gmail.com>> wrote: > 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 <mailto: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 <mailto: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 <mailto:es-discuss at mozilla.org> > https://mail.mozilla.org/listinfo/es-discuss <https://mail.mozilla.org/listinfo/es-discuss> > > > > > _______________________________________________ > es-discuss mailing list > es-discuss at mozilla.org <mailto:es-discuss at mozilla.org> > https://mail.mozilla.org/listinfo/es-discuss <https://mail.mozilla.org/listinfo/es-discuss> > > > _______________________________________________ > 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/9a93ed3a/attachment-0001.html>
I agree with Allen. I am certainly willing -- often eager -- to revisit and revise old design decisions that are considered done, when I think the cost of leaving it alone exceeds the cost of changing it. In this case, the arguments that this extra parameter would be an improvement seem weak. Even without the revising-old-decision costs, I am uncertain which decision I would prefer. Given these costs, it seems clear we should leave this one alone.
Unless it turns out that the cost of leaving it alone is much greater than I have understood. If so, please help me see what I'm missing.
I agree with Allen. I am certainly willing -- often eager -- to revisit and revise old design decisions that are considered done, when I think the cost of leaving it alone exceeds the cost of changing it. In this case, the arguments that this extra parameter would be an improvement seem weak. Even without the revising-old-decision costs, I am uncertain which decision I would prefer. Given these costs, it seems clear we should leave this one alone. Unless it turns out that the cost of leaving it alone is much greater than I have understood. If so, please help me see what I'm missing. On Fri, Mar 18, 2016 at 12:17 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote: > > On Mar 18, 2016, at 9:24 AM, Andrea Giammarchi < > andrea.giammarchi at gmail.com> wrote: > > Agreed with everybody else the `receiver` is always needed and `Proxy` on > the prototype makes way more sense than per instance. > > > I don’t agree. While you certainly can imagine a language where each > object’s “prototype” determines that object’s fundamental behaviors and > provides the MOP intercession hooks(in fact that’s how most class-based > languages work). But that’s not the JS object model. Each JS object is > essentially a singleton that defines it’s own fundamental behaviors. > Whether or this model is better or worse than the class-based model isn't > really relevant, but in the context of JS there are advantage to > consistently adhering to that model, > > For example, in Michael’s desired approach, the instance objects of his > ArrayView abstraction are “ordinary objects”. One of the fundamental > behavioral characteristics of ordinary objects is that all of there own > properties are defined and available to the implementation in a standard > way. Implementations certainly make use of that characteristic for > optimization purposes. Michael’s approach would make such optimizations > invalid because every time an own property needed to be access a prototype > walk would have to be performed because there might be an exotic object > somewhere on the prototype chain that was injecting own property into the > original “receiver”. > > Michael’s preferred approach also introduces observable irregularity into > the standard JS inheritance model for ordinary objects. > > Consider an object created using Michael’s preferred approach: > > ```js > var arr = [0, 1]; > console.log(Reflect.has(arr,”0”)); //true, arr has “0” as an own property > var subArr = Object.create(arr); > console.log(Reflect.has(subArr,”0”)); //true, all unshadowed properties > of proto visible from ordinary objects > > var b = new ArrayView2(arr); > console.log(Reflect.has(b,”0”)); //true, prototype proxy makes array > elements visible as if properties of b > var subB= Object.create(b); > console.log(Reflect.has(subB,”0”)); //false, some unshadowed properties > of proto is not visible from subB > ``` > > Note the his original Proxy implementation does not have this undesirable > characteristic. > > So what about the use of `receiver` in [[Get]]/[[Set]]. That’s a > different situation. [[Get]]/[[Set]] are not fundamental, rather they are > derived (they work by applying other more fundamental MOP operations). The > `receiver` argument is not used by them to perform property lookup (they > use [[GetOwnProperty]] and [[GetPrototypeOf]]) for the actual property > lookup). `receiver` is only used in the semantics of what happens after > the property lookup occurs. Adding a `receiver` argument to the other MOP > operations for the purpose of changing property lookup semantics seems like > a step too far. The ES MOP design is a balancing act between capability, > implementability, and consistency. I think adding `receiver` to every MOP > operation would throw the design out of balance. > > Finally, > > Note that we are not really talking about a new capability here. > Michael’s first design shows that ES proxies already have the capability to > implement the object level semantics he desires. So, we are only talking > about exactly how he goes about using Proxy to implement that semantics. He > would prefer a different Proxy design than what was actually provided by > ES6. But that isn’t what was specified or what has now been implemented. We > can all imagine how many JS features might be “better” if they worked > somewhat differently. But that generally isn’t an option. The existing > language features and their implementations are what they are and as JS > programmers we need to work within that reality. > > Allen > > > > > > > > > > > > > > > > > > > > > > Also the `getPrototypeOf` trap is really pointless right now > > ```js > function Yak() {} > Yak.prototype = new Proxy(Yak.prototype, { > getPrototypeOf: (target) => console.log('lulz') > }); > > var yup = new Yak; > Object.getPrototypeOf(yup); > ``` > > The `target` is actually the original `Yak.prototype` which is already the > `yup` prototype: useless trap if used in such way. > > Being also unable to distinguish between `getOwnPropertyNames` vs `keys` > is a bit weird. > > `Proxy` looks so close to be that powerful but these bits make it kinda > useless for most real-world cases I've been recently dealing with. > > Thanks for any sort of improvement. > > Regards > > > > > On Fri, Mar 18, 2016 at 1:54 PM, Michael Theriot < > michael.lee.theriot at gmail.com> wrote: > >> 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 >>>> >>>> >>> >> >> _______________________________________________ >> es-discuss mailing list >> es-discuss at mozilla.org >> https://mail.mozilla.org/listinfo/es-discuss >> >> > _______________________________________________ > es-discuss mailing list > es-discuss at mozilla.org > https://mail.mozilla.org/listinfo/es-discuss > > > > _______________________________________________ > es-discuss mailing list > es-discuss at mozilla.org > https://mail.mozilla.org/listinfo/es-discuss > > -- Cheers, --MarkM -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160318/6cb16291/attachment-0001.html>
AFAIK the reason there is a receiver
is to deal with prototype cases ...
if that was a good enough reason to have one, every prototype case should
be considered for consistency sake.
We've been advocating prototypal inheritance for 20 years and now it's an obstacle or "not how JS is"?
class Magic extends new Proxy(unbe, lievable) {
// please make it happen
// as it is now, that won't work at all
}
Best
AFAIK the reason there is a `receiver` is to deal with prototype cases ... if that was a good enough reason to have one, every prototype case should be considered for consistency sake. We've been advocating prototypal inheritance for 20 years and now it's an obstacle or "not how JS is"? ```js class Magic extends new Proxy(unbe, lievable) { // please make it happen // as it is now, that won't work at all } ``` Best Regards On Fri, Mar 18, 2016 at 7:30 PM, Mark S. Miller <erights at google.com> wrote: > I agree with Allen. I am certainly willing -- often eager -- to revisit > and revise old design decisions that are considered done, when I think the > cost of leaving it alone exceeds the cost of changing it. In this case, the > arguments that this extra parameter would be an improvement seem weak. Even > without the revising-old-decision costs, I am uncertain which decision I > would prefer. Given these costs, it seems clear we should leave this one > alone. > > Unless it turns out that the cost of leaving it alone is much greater than > I have understood. If so, please help me see what I'm missing. > > > > > > > On Fri, Mar 18, 2016 at 12:17 PM, Allen Wirfs-Brock <allen at wirfs-brock.com > > wrote: > >> >> On Mar 18, 2016, at 9:24 AM, Andrea Giammarchi < >> andrea.giammarchi at gmail.com> wrote: >> >> Agreed with everybody else the `receiver` is always needed and `Proxy` on >> the prototype makes way more sense than per instance. >> >> >> I don’t agree. While you certainly can imagine a language where each >> object’s “prototype” determines that object’s fundamental behaviors and >> provides the MOP intercession hooks(in fact that’s how most class-based >> languages work). But that’s not the JS object model. Each JS object is >> essentially a singleton that defines it’s own fundamental behaviors. >> Whether or this model is better or worse than the class-based model isn't >> really relevant, but in the context of JS there are advantage to >> consistently adhering to that model, >> >> For example, in Michael’s desired approach, the instance objects of his >> ArrayView abstraction are “ordinary objects”. One of the fundamental >> behavioral characteristics of ordinary objects is that all of there own >> properties are defined and available to the implementation in a standard >> way. Implementations certainly make use of that characteristic for >> optimization purposes. Michael’s approach would make such optimizations >> invalid because every time an own property needed to be access a prototype >> walk would have to be performed because there might be an exotic object >> somewhere on the prototype chain that was injecting own property into the >> original “receiver”. >> >> Michael’s preferred approach also introduces observable irregularity into >> the standard JS inheritance model for ordinary objects. >> >> Consider an object created using Michael’s preferred approach: >> >> ```js >> var arr = [0, 1]; >> console.log(Reflect.has(arr,”0”)); //true, arr has “0” as an own property >> var subArr = Object.create(arr); >> console.log(Reflect.has(subArr,”0”)); //true, all unshadowed properties >> of proto visible from ordinary objects >> >> var b = new ArrayView2(arr); >> console.log(Reflect.has(b,”0”)); //true, prototype proxy makes array >> elements visible as if properties of b >> var subB= Object.create(b); >> console.log(Reflect.has(subB,”0”)); //false, some unshadowed properties >> of proto is not visible from subB >> ``` >> >> Note the his original Proxy implementation does not have this undesirable >> characteristic. >> >> So what about the use of `receiver` in [[Get]]/[[Set]]. That’s a >> different situation. [[Get]]/[[Set]] are not fundamental, rather they are >> derived (they work by applying other more fundamental MOP operations). The >> `receiver` argument is not used by them to perform property lookup (they >> use [[GetOwnProperty]] and [[GetPrototypeOf]]) for the actual property >> lookup). `receiver` is only used in the semantics of what happens after >> the property lookup occurs. Adding a `receiver` argument to the other MOP >> operations for the purpose of changing property lookup semantics seems like >> a step too far. The ES MOP design is a balancing act between capability, >> implementability, and consistency. I think adding `receiver` to every MOP >> operation would throw the design out of balance. >> >> Finally, >> >> Note that we are not really talking about a new capability here. >> Michael’s first design shows that ES proxies already have the capability to >> implement the object level semantics he desires. So, we are only talking >> about exactly how he goes about using Proxy to implement that semantics. He >> would prefer a different Proxy design than what was actually provided by >> ES6. But that isn’t what was specified or what has now been implemented. We >> can all imagine how many JS features might be “better” if they worked >> somewhat differently. But that generally isn’t an option. The existing >> language features and their implementations are what they are and as JS >> programmers we need to work within that reality. >> >> Allen >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> Also the `getPrototypeOf` trap is really pointless right now >> >> ```js >> function Yak() {} >> Yak.prototype = new Proxy(Yak.prototype, { >> getPrototypeOf: (target) => console.log('lulz') >> }); >> >> var yup = new Yak; >> Object.getPrototypeOf(yup); >> ``` >> >> The `target` is actually the original `Yak.prototype` which is already >> the `yup` prototype: useless trap if used in such way. >> >> Being also unable to distinguish between `getOwnPropertyNames` vs `keys` >> is a bit weird. >> >> `Proxy` looks so close to be that powerful but these bits make it kinda >> useless for most real-world cases I've been recently dealing with. >> >> Thanks for any sort of improvement. >> >> Regards >> >> >> >> >> On Fri, Mar 18, 2016 at 1:54 PM, Michael Theriot < >> michael.lee.theriot at gmail.com> wrote: >> >>> 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 >>>>> >>>>> >>>> >>> >>> _______________________________________________ >>> es-discuss mailing list >>> es-discuss at mozilla.org >>> https://mail.mozilla.org/listinfo/es-discuss >>> >>> >> _______________________________________________ >> es-discuss mailing list >> es-discuss at mozilla.org >> https://mail.mozilla.org/listinfo/es-discuss >> >> >> >> _______________________________________________ >> es-discuss mailing list >> es-discuss at mozilla.org >> https://mail.mozilla.org/listinfo/es-discuss >> >> > > > -- > Cheers, > --MarkM > -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160318/d918c966/attachment-0001.html>
Michael’s preferred approach also introduces observable irregularity into the standard JS inheritance model for ordinary objects. Consider an object created using Michael’s preferred approach:
var arr = [0, 1]; console.log(Reflect.has(arr,”0”)); //true, arr has “0” as an own property var subArr = Object.create(arr); console.log(Reflect.has(subArr,”0”)); //true, all unshadowed properties of proto visible from ordinary objects var b = new ArrayView2(arr); console.log(Reflect.has(b,”0”)); //true, prototype proxy makes array elements visible as if properties of b var subB= Object.create(b); console.log(Reflect.has(subB,”0”)); //false, some unshadowed properties of proto is not visible from subB
I think this relates to the original concern; if you could pass the
receiver this could be resolved. That still leaves getOwnPropertyNames
reporting wrong values though and I see no foreseeable way to resolve that
without returning an actual proxy itself.
The reason I'm trying this approach is because I read on the MDN that when
used in the prototype a receiver
argument is passed that references the
instance, so I assumed this was the intent behind it. The only other
explanation I could think of is that proxies have a receiver to mimic the
Reflect.set
/Reflect.get
methods which need a receiver for
getters/setters to work properly, not so you can use them on the prototype
chain.
The other case I would make is every instance would have an identical proxy, and it just makes sense to put that on the prototype for the same reasons you put shared methods/properties there.
Note that we are not really talking about a new capability here. Michael’s first design shows that ES proxies already have the capability to implement the object level semantics he desires.
To be fair I had several obstacles with inheritance using the first version.
var wm1 = new WeakMap();
function A() {
wm1.set(this, {});
return new Proxy(this, {});
}
var wm2 = new WeakMap();
function B() {
A.call(this);
wm2.set(this, {});
return new Proxy(this, {});
}
var a = new A();
var b = new B();
wm1.has(a); // true
wm2.has(a); // false
wm1.has(b); // false
wm2.has(b); // true
As you can see storing a reference to this
can't work anymore, since we
actually return a proxy. You can try to work around this...
var wm1 = new WeakMap();
function A() {
let self = this;
if(new.target === A) {
self = new Proxy(this, {});
}
wm1.set(self, {});
return self;
}
var wm2 = new WeakMap();
function B() {
let self = this;
if(new.target === B) {
self = new Proxy(this, {});
}
A.call(self);
wm2.set(self, {});
return self;
}
var a = new A();
var b = new B();
wm1.has(a); // true
wm2.has(a); // false
wm1.has(b); // true
wm2.has(b); // true
But then problems arise because the new proxy doesn't go through the old proxy. So anything guaranteed by A()'s proxy is not guaranteed by B()'s proxy.
var wm1 = new WeakMap();
function A() {
let self = this;
if(new.target === A) {
self = new Proxy(this, {
get: (target, property, receiver) => property === 'bacon' || target[property]
});
}
wm1.set(self, {});
return self;
}
var wm2 = new WeakMap();
function B() {
let self = this;
if(new.target === B) {
self = new Proxy(this, {
get: (target, property, receiver) => property === 'ham' || target[property]
});
}
A.call(self);
wm2.set(self, {});
return self;
}
var a = new A();
var b = new B();
wm1.has(a); // true
wm2.has(a); // false
wm1.has(b); // true
wm2.has(b); // true
a.bacon; // true
a.ham; // undefined
b.bacon; // undefined
b.ham; // true
(I'm open to solutions on this particular case... One that doesn't require me to leak the handler of the A proxy)
Ultimately I can actually achieve both what I want with the ArrayView
example and inheritance by using a lot of defineProperty
calls on
this
in the constructor, but performance is a disaster as you might
expect.
> > Michael’s preferred approach also introduces observable irregularity into > the standard JS inheritance model for ordinary objects. > Consider an object created using Michael’s preferred approach: > ```js > var arr = [0, 1]; > console.log(Reflect.has(arr,”0”)); //true, arr has “0” as an own property > var subArr = Object.create(arr); > console.log(Reflect.has(subArr,”0”)); //true, all unshadowed properties > of proto visible from ordinary objects > var b = new ArrayView2(arr); > console.log(Reflect.has(b,”0”)); //true, prototype proxy makes array > elements visible as if properties of b > var subB= Object.create(b); > console.log(Reflect.has(subB,”0”)); //false, some unshadowed properties > of proto is not visible from subB > ``` I think this relates to the original concern; if you could pass the receiver this could be resolved. That still leaves `getOwnPropertyNames` reporting wrong values though and I see no foreseeable way to resolve that without returning an actual proxy itself. The reason I'm trying this approach is because I read on the MDN that when used in the prototype a `receiver` argument is passed that references the instance, so I assumed this was the intent behind it. The only other explanation I could think of is that proxies have a receiver to mimic the `Reflect.set`/`Reflect.get` methods which need a receiver for getters/setters to work properly, not so you can use them on the prototype chain. The other case I would make is every instance would have an identical proxy, and it just makes sense to put that on the prototype for the same reasons you put shared methods/properties there. Note that we are not really talking about a new capability here. Michael’s > first design shows that ES proxies already have the capability to implement > the object level semantics he desires. To be fair I had several obstacles with inheritance using the first version. ```js var wm1 = new WeakMap(); function A() { wm1.set(this, {}); return new Proxy(this, {}); } var wm2 = new WeakMap(); function B() { A.call(this); wm2.set(this, {}); return new Proxy(this, {}); } var a = new A(); var b = new B(); wm1.has(a); // true wm2.has(a); // false wm1.has(b); // false wm2.has(b); // true ``` As you can see storing a reference to `this` can't work anymore, since we actually return a proxy. You can try to work around this... ```js var wm1 = new WeakMap(); function A() { let self = this; if(new.target === A) { self = new Proxy(this, {}); } wm1.set(self, {}); return self; } var wm2 = new WeakMap(); function B() { let self = this; if(new.target === B) { self = new Proxy(this, {}); } A.call(self); wm2.set(self, {}); return self; } var a = new A(); var b = new B(); wm1.has(a); // true wm2.has(a); // false wm1.has(b); // true wm2.has(b); // true ``` But then problems arise because the new proxy doesn't go through the old proxy. So anything guaranteed by A()'s proxy is not guaranteed by B()'s proxy. ```js var wm1 = new WeakMap(); function A() { let self = this; if(new.target === A) { self = new Proxy(this, { get: (target, property, receiver) => property === 'bacon' || target[property] }); } wm1.set(self, {}); return self; } var wm2 = new WeakMap(); function B() { let self = this; if(new.target === B) { self = new Proxy(this, { get: (target, property, receiver) => property === 'ham' || target[property] }); } A.call(self); wm2.set(self, {}); return self; } var a = new A(); var b = new B(); wm1.has(a); // true wm2.has(a); // false wm1.has(b); // true wm2.has(b); // true a.bacon; // true a.ham; // undefined b.bacon; // undefined b.ham; // true ``` (I'm open to solutions on this particular case... One that doesn't require me to leak the handler of the A proxy) Ultimately I can actually achieve both what I want with the ArrayView example and inheritance by using a **lot** of `defineProperty` calls on `this` in the constructor, but performance is a disaster as you might expect. On Fri, Mar 18, 2016 at 2:55 PM, Andrea Giammarchi < andrea.giammarchi at gmail.com> wrote: > AFAIK the reason there is a `receiver` is to deal with prototype cases ... > if that was a good enough reason to have one, every prototype case should > be considered for consistency sake. > > We've been advocating prototypal inheritance for 20 years and now it's an > obstacle or "not how JS is"? > > ```js > class Magic extends new Proxy(unbe, lievable) { > // please make it happen > // as it is now, that won't work at all > } > ``` > > Best Regards > > > On Fri, Mar 18, 2016 at 7:30 PM, Mark S. Miller <erights at google.com> > wrote: > >> I agree with Allen. I am certainly willing -- often eager -- to revisit >> and revise old design decisions that are considered done, when I think the >> cost of leaving it alone exceeds the cost of changing it. In this case, the >> arguments that this extra parameter would be an improvement seem weak. Even >> without the revising-old-decision costs, I am uncertain which decision I >> would prefer. Given these costs, it seems clear we should leave this one >> alone. >> >> Unless it turns out that the cost of leaving it alone is much greater >> than I have understood. If so, please help me see what I'm missing. >> >> >> >> >> >> >> On Fri, Mar 18, 2016 at 12:17 PM, Allen Wirfs-Brock < >> allen at wirfs-brock.com> wrote: >> >>> >>> On Mar 18, 2016, at 9:24 AM, Andrea Giammarchi < >>> andrea.giammarchi at gmail.com> wrote: >>> >>> Agreed with everybody else the `receiver` is always needed and `Proxy` >>> on the prototype makes way more sense than per instance. >>> >>> >>> I don’t agree. While you certainly can imagine a language where each >>> object’s “prototype” determines that object’s fundamental behaviors and >>> provides the MOP intercession hooks(in fact that’s how most class-based >>> languages work). But that’s not the JS object model. Each JS object is >>> essentially a singleton that defines it’s own fundamental behaviors. >>> Whether or this model is better or worse than the class-based model isn't >>> really relevant, but in the context of JS there are advantage to >>> consistently adhering to that model, >>> >>> For example, in Michael’s desired approach, the instance objects of his >>> ArrayView abstraction are “ordinary objects”. One of the fundamental >>> behavioral characteristics of ordinary objects is that all of there own >>> properties are defined and available to the implementation in a standard >>> way. Implementations certainly make use of that characteristic for >>> optimization purposes. Michael’s approach would make such optimizations >>> invalid because every time an own property needed to be access a prototype >>> walk would have to be performed because there might be an exotic object >>> somewhere on the prototype chain that was injecting own property into the >>> original “receiver”. >>> >>> Michael’s preferred approach also introduces observable irregularity >>> into the standard JS inheritance model for ordinary objects. >>> >>> Consider an object created using Michael’s preferred approach: >>> >>> ```js >>> var arr = [0, 1]; >>> console.log(Reflect.has(arr,”0”)); //true, arr has “0” as an own >>> property >>> var subArr = Object.create(arr); >>> console.log(Reflect.has(subArr,”0”)); //true, all unshadowed properties >>> of proto visible from ordinary objects >>> >>> var b = new ArrayView2(arr); >>> console.log(Reflect.has(b,”0”)); //true, prototype proxy makes array >>> elements visible as if properties of b >>> var subB= Object.create(b); >>> console.log(Reflect.has(subB,”0”)); //false, some unshadowed properties >>> of proto is not visible from subB >>> ``` >>> >>> Note the his original Proxy implementation does not have this >>> undesirable characteristic. >>> >>> So what about the use of `receiver` in [[Get]]/[[Set]]. That’s a >>> different situation. [[Get]]/[[Set]] are not fundamental, rather they are >>> derived (they work by applying other more fundamental MOP operations). The >>> `receiver` argument is not used by them to perform property lookup (they >>> use [[GetOwnProperty]] and [[GetPrototypeOf]]) for the actual property >>> lookup). `receiver` is only used in the semantics of what happens after >>> the property lookup occurs. Adding a `receiver` argument to the other MOP >>> operations for the purpose of changing property lookup semantics seems like >>> a step too far. The ES MOP design is a balancing act between capability, >>> implementability, and consistency. I think adding `receiver` to every MOP >>> operation would throw the design out of balance. >>> >>> Finally, >>> >>> Note that we are not really talking about a new capability here. >>> Michael’s first design shows that ES proxies already have the capability to >>> implement the object level semantics he desires. So, we are only talking >>> about exactly how he goes about using Proxy to implement that semantics. He >>> would prefer a different Proxy design than what was actually provided by >>> ES6. But that isn’t what was specified or what has now been implemented. We >>> can all imagine how many JS features might be “better” if they worked >>> somewhat differently. But that generally isn’t an option. The existing >>> language features and their implementations are what they are and as JS >>> programmers we need to work within that reality. >>> >>> Allen >>> >>> >>> >>> >>> >>> >>> >>> >>> >>> >>> >>> >>> >>> >>> >>> >>> >>> >>> >>> >>> >>> Also the `getPrototypeOf` trap is really pointless right now >>> >>> ```js >>> function Yak() {} >>> Yak.prototype = new Proxy(Yak.prototype, { >>> getPrototypeOf: (target) => console.log('lulz') >>> }); >>> >>> var yup = new Yak; >>> Object.getPrototypeOf(yup); >>> ``` >>> >>> The `target` is actually the original `Yak.prototype` which is already >>> the `yup` prototype: useless trap if used in such way. >>> >>> Being also unable to distinguish between `getOwnPropertyNames` vs `keys` >>> is a bit weird. >>> >>> `Proxy` looks so close to be that powerful but these bits make it kinda >>> useless for most real-world cases I've been recently dealing with. >>> >>> Thanks for any sort of improvement. >>> >>> Regards >>> >>> >>> >>> >>> On Fri, Mar 18, 2016 at 1:54 PM, Michael Theriot < >>> michael.lee.theriot at gmail.com> wrote: >>> >>>> 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 >>>>>> >>>>>> >>>>> >>>> >>>> _______________________________________________ >>>> es-discuss mailing list >>>> es-discuss at mozilla.org >>>> https://mail.mozilla.org/listinfo/es-discuss >>>> >>>> >>> _______________________________________________ >>> es-discuss mailing list >>> es-discuss at mozilla.org >>> https://mail.mozilla.org/listinfo/es-discuss >>> >>> >>> >>> _______________________________________________ >>> es-discuss mailing list >>> es-discuss at mozilla.org >>> https://mail.mozilla.org/listinfo/es-discuss >>> >>> >> >> >> -- >> Cheers, >> --MarkM >> > > > _______________________________________________ > 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/2ee2cd35/attachment-0001.html>
I think I figured out how to make inheritance work...
var wm1 = new WeakMap();
function A() {
let proxy = new Proxy(this, {
get: (target, property, receiver) => property === 'bacon' || target[property]
});
wm1.set(proxy, {});
return proxy;
}
var wm2 = new WeakMap();
function B() {
let proxy = A.call(new Proxy(this, {
get: (target, property, receiver) => property === 'ham' || target[property]
}));
wm2.set(proxy, {});
return proxy;
}
var a = new A();
var b = new B();
wm1.has(a); // true
wm2.has(a); // false
wm1.has(b); // true
wm2.has(b); // true
a.bacon; // true
a.ham; // undefined
b.bacon; // true
b.ham; // true
I can't imagine this is good for optimizations though. But I guess it does what I need.
Edit:
Actually I seem to have it backwards... I believe the correct way would be the following:
function B() {
let proxy = new Proxy(A.call(this), {
get: (target, property, receiver) => property === 'ham' || target[property]
});
wm2.set(proxy, {});
return proxy;
}
I think I figured out how to make inheritance work... ```js var wm1 = new WeakMap(); function A() { let proxy = new Proxy(this, { get: (target, property, receiver) => property === 'bacon' || target[property] }); wm1.set(proxy, {}); return proxy; } var wm2 = new WeakMap(); function B() { let proxy = A.call(new Proxy(this, { get: (target, property, receiver) => property === 'ham' || target[property] })); wm2.set(proxy, {}); return proxy; } var a = new A(); var b = new B(); wm1.has(a); // true wm2.has(a); // false wm1.has(b); // true wm2.has(b); // true a.bacon; // true a.ham; // undefined b.bacon; // true b.ham; // true ``` I can't imagine this is good for optimizations though. But I guess it does what I need. On Fri, Mar 18, 2016 at 4:45 PM, Michael Theriot < michael.lee.theriot at gmail.com> wrote: > Michael’s preferred approach also introduces observable irregularity into >> the standard JS inheritance model for ordinary objects. >> Consider an object created using Michael’s preferred approach: >> ```js >> var arr = [0, 1]; >> console.log(Reflect.has(arr,”0”)); //true, arr has “0” as an own property >> var subArr = Object.create(arr); >> console.log(Reflect.has(subArr,”0”)); //true, all unshadowed properties >> of proto visible from ordinary objects >> var b = new ArrayView2(arr); >> console.log(Reflect.has(b,”0”)); //true, prototype proxy makes array >> elements visible as if properties of b >> var subB= Object.create(b); >> console.log(Reflect.has(subB,”0”)); //false, some unshadowed properties >> of proto is not visible from subB >> ``` > > > I think this relates to the original concern; if you could pass the > receiver this could be resolved. That still leaves `getOwnPropertyNames` > reporting wrong values though and I see no foreseeable way to resolve that > without returning an actual proxy itself. > > The reason I'm trying this approach is because I read on the MDN that when > used in the prototype a `receiver` argument is passed that references the > instance, so I assumed this was the intent behind it. The only other > explanation I could think of is that proxies have a receiver to mimic the > `Reflect.set`/`Reflect.get` methods which need a receiver for > getters/setters to work properly, not so you can use them on the prototype > chain. > > The other case I would make is every instance would have an identical > proxy, and it just makes sense to put that on the prototype for the same > reasons you put shared methods/properties there. > > Note that we are not really talking about a new capability here. Michael’s >> first design shows that ES proxies already have the capability to implement >> the object level semantics he desires. > > > To be fair I had several obstacles with inheritance using the first > version. > > ```js > var wm1 = new WeakMap(); > > function A() { > wm1.set(this, {}); > return new Proxy(this, {}); > } > > var wm2 = new WeakMap(); > > function B() { > A.call(this); > wm2.set(this, {}); > return new Proxy(this, {}); > } > > var a = new A(); > var b = new B(); > > wm1.has(a); // true > wm2.has(a); // false > > wm1.has(b); // false > wm2.has(b); // true > ``` > > As you can see storing a reference to `this` can't work anymore, since we > actually return a proxy. You can try to work around this... > > ```js > var wm1 = new WeakMap(); > > function A() { > let self = this; > if(new.target === A) { > self = new Proxy(this, {}); > } > wm1.set(self, {}); > return self; > } > > var wm2 = new WeakMap(); > > function B() { > let self = this; > if(new.target === B) { > self = new Proxy(this, {}); > } > A.call(self); > wm2.set(self, {}); > return self; > } > > var a = new A(); > var b = new B(); > > wm1.has(a); // true > wm2.has(a); // false > > wm1.has(b); // true > wm2.has(b); // true > ``` > > But then problems arise because the new proxy doesn't go through the old > proxy. So anything guaranteed by A()'s proxy is not guaranteed by B()'s > proxy. > > ```js > var wm1 = new WeakMap(); > > function A() { > let self = this; > if(new.target === A) { > self = new Proxy(this, { > get: (target, property, receiver) => property === 'bacon' || > target[property] > }); > } > wm1.set(self, {}); > return self; > } > > var wm2 = new WeakMap(); > > function B() { > let self = this; > if(new.target === B) { > self = new Proxy(this, { > get: (target, property, receiver) => property === 'ham' || > target[property] > }); > } > A.call(self); > wm2.set(self, {}); > return self; > } > > var a = new A(); > var b = new B(); > > wm1.has(a); // true > wm2.has(a); // false > > wm1.has(b); // true > wm2.has(b); // true > > a.bacon; // true > a.ham; // undefined > > b.bacon; // undefined > b.ham; // true > ``` > > (I'm open to solutions on this particular case... One that doesn't require > me to leak the handler of the A proxy) > > Ultimately I can actually achieve both what I want with the ArrayView > example and inheritance by using a **lot** of `defineProperty` calls on > `this` in the constructor, but performance is a disaster as you might > expect. > > On Fri, Mar 18, 2016 at 2:55 PM, Andrea Giammarchi < > andrea.giammarchi at gmail.com> wrote: > >> AFAIK the reason there is a `receiver` is to deal with prototype cases >> ... if that was a good enough reason to have one, every prototype case >> should be considered for consistency sake. >> >> We've been advocating prototypal inheritance for 20 years and now it's an >> obstacle or "not how JS is"? >> >> ```js >> class Magic extends new Proxy(unbe, lievable) { >> // please make it happen >> // as it is now, that won't work at all >> } >> ``` >> >> Best Regards >> >> >> On Fri, Mar 18, 2016 at 7:30 PM, Mark S. Miller <erights at google.com> >> wrote: >> >>> I agree with Allen. I am certainly willing -- often eager -- to revisit >>> and revise old design decisions that are considered done, when I think the >>> cost of leaving it alone exceeds the cost of changing it. In this case, the >>> arguments that this extra parameter would be an improvement seem weak. Even >>> without the revising-old-decision costs, I am uncertain which decision I >>> would prefer. Given these costs, it seems clear we should leave this one >>> alone. >>> >>> Unless it turns out that the cost of leaving it alone is much greater >>> than I have understood. If so, please help me see what I'm missing. >>> >>> >>> >>> >>> >>> >>> On Fri, Mar 18, 2016 at 12:17 PM, Allen Wirfs-Brock < >>> allen at wirfs-brock.com> wrote: >>> >>>> >>>> On Mar 18, 2016, at 9:24 AM, Andrea Giammarchi < >>>> andrea.giammarchi at gmail.com> wrote: >>>> >>>> Agreed with everybody else the `receiver` is always needed and `Proxy` >>>> on the prototype makes way more sense than per instance. >>>> >>>> >>>> I don’t agree. While you certainly can imagine a language where each >>>> object’s “prototype” determines that object’s fundamental behaviors and >>>> provides the MOP intercession hooks(in fact that’s how most class-based >>>> languages work). But that’s not the JS object model. Each JS object is >>>> essentially a singleton that defines it’s own fundamental behaviors. >>>> Whether or this model is better or worse than the class-based model isn't >>>> really relevant, but in the context of JS there are advantage to >>>> consistently adhering to that model, >>>> >>>> For example, in Michael’s desired approach, the instance objects of >>>> his ArrayView abstraction are “ordinary objects”. One of the fundamental >>>> behavioral characteristics of ordinary objects is that all of there own >>>> properties are defined and available to the implementation in a standard >>>> way. Implementations certainly make use of that characteristic for >>>> optimization purposes. Michael’s approach would make such optimizations >>>> invalid because every time an own property needed to be access a prototype >>>> walk would have to be performed because there might be an exotic object >>>> somewhere on the prototype chain that was injecting own property into the >>>> original “receiver”. >>>> >>>> Michael’s preferred approach also introduces observable irregularity >>>> into the standard JS inheritance model for ordinary objects. >>>> >>>> Consider an object created using Michael’s preferred approach: >>>> >>>> ```js >>>> var arr = [0, 1]; >>>> console.log(Reflect.has(arr,”0”)); //true, arr has “0” as an own >>>> property >>>> var subArr = Object.create(arr); >>>> console.log(Reflect.has(subArr,”0”)); //true, all unshadowed >>>> properties of proto visible from ordinary objects >>>> >>>> var b = new ArrayView2(arr); >>>> console.log(Reflect.has(b,”0”)); //true, prototype proxy makes array >>>> elements visible as if properties of b >>>> var subB= Object.create(b); >>>> console.log(Reflect.has(subB,”0”)); //false, some unshadowed >>>> properties of proto is not visible from subB >>>> ``` >>>> >>>> Note the his original Proxy implementation does not have this >>>> undesirable characteristic. >>>> >>>> So what about the use of `receiver` in [[Get]]/[[Set]]. That’s a >>>> different situation. [[Get]]/[[Set]] are not fundamental, rather they are >>>> derived (they work by applying other more fundamental MOP operations). The >>>> `receiver` argument is not used by them to perform property lookup (they >>>> use [[GetOwnProperty]] and [[GetPrototypeOf]]) for the actual property >>>> lookup). `receiver` is only used in the semantics of what happens after >>>> the property lookup occurs. Adding a `receiver` argument to the other MOP >>>> operations for the purpose of changing property lookup semantics seems like >>>> a step too far. The ES MOP design is a balancing act between capability, >>>> implementability, and consistency. I think adding `receiver` to every MOP >>>> operation would throw the design out of balance. >>>> >>>> Finally, >>>> >>>> Note that we are not really talking about a new capability here. >>>> Michael’s first design shows that ES proxies already have the capability to >>>> implement the object level semantics he desires. So, we are only talking >>>> about exactly how he goes about using Proxy to implement that semantics. He >>>> would prefer a different Proxy design than what was actually provided by >>>> ES6. But that isn’t what was specified or what has now been implemented. We >>>> can all imagine how many JS features might be “better” if they worked >>>> somewhat differently. But that generally isn’t an option. The existing >>>> language features and their implementations are what they are and as JS >>>> programmers we need to work within that reality. >>>> >>>> Allen >>>> >>>> >>>> >>>> >>>> >>>> >>>> >>>> >>>> >>>> >>>> >>>> >>>> >>>> >>>> >>>> >>>> >>>> >>>> >>>> >>>> >>>> Also the `getPrototypeOf` trap is really pointless right now >>>> >>>> ```js >>>> function Yak() {} >>>> Yak.prototype = new Proxy(Yak.prototype, { >>>> getPrototypeOf: (target) => console.log('lulz') >>>> }); >>>> >>>> var yup = new Yak; >>>> Object.getPrototypeOf(yup); >>>> ``` >>>> >>>> The `target` is actually the original `Yak.prototype` which is already >>>> the `yup` prototype: useless trap if used in such way. >>>> >>>> Being also unable to distinguish between `getOwnPropertyNames` vs >>>> `keys` is a bit weird. >>>> >>>> `Proxy` looks so close to be that powerful but these bits make it kinda >>>> useless for most real-world cases I've been recently dealing with. >>>> >>>> Thanks for any sort of improvement. >>>> >>>> Regards >>>> >>>> >>>> >>>> >>>> On Fri, Mar 18, 2016 at 1:54 PM, Michael Theriot < >>>> michael.lee.theriot at gmail.com> wrote: >>>> >>>>> 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 >>>>>>> >>>>>>> >>>>>> >>>>> >>>>> _______________________________________________ >>>>> es-discuss mailing list >>>>> es-discuss at mozilla.org >>>>> https://mail.mozilla.org/listinfo/es-discuss >>>>> >>>>> >>>> _______________________________________________ >>>> es-discuss mailing list >>>> es-discuss at mozilla.org >>>> https://mail.mozilla.org/listinfo/es-discuss >>>> >>>> >>>> >>>> _______________________________________________ >>>> es-discuss mailing list >>>> es-discuss at mozilla.org >>>> https://mail.mozilla.org/listinfo/es-discuss >>>> >>>> >>> >>> >>> -- >>> Cheers, >>> --MarkM >>> >> >> >> _______________________________________________ >> 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/99184ad3/attachment-0001.html>
I was on board with potentially making has()
receiver-dependent but you
lost me when you expected getOwnPropertyNames
to trigger a trap on a
proxy-as-prototype. Adding receiver
to every MOP method is a no-go. It
fundamentally changes the meaning of these operations and it would destroy
the pay-only-when-you-use-it performance model of proxies, since operations
that used to be local to only the 'own' object would now need to search the
proto-chain for a potential proxy trap.
Using a proxy-as-prototype was never intended as a way to be able to intercept arbitrary operations on whatever object happened to inherit from the proxy. A proxy-as-prototype still emulates only a single object. It just happens to be an object that serves as a prototype for other objects.
Cheers, Tom
2016-03-18 23:18 GMT+01:00 Michael Theriot <michael.lee.theriot at gmail.com>:
I was on board with potentially making `has()` receiver-dependent but you lost me when you expected `getOwnPropertyNames` to trigger a trap on a proxy-as-prototype. Adding `receiver` to every MOP method is a no-go. It fundamentally changes the meaning of these operations and it would destroy the pay-only-when-you-use-it performance model of proxies, since operations that used to be local to only the 'own' object would now need to search the proto-chain for a potential proxy trap. Using a proxy-as-prototype was never intended as a way to be able to intercept arbitrary operations on whatever object happened to inherit from the proxy. A proxy-as-prototype still emulates only a single object. It just happens to be an object that serves as a prototype for other objects. Cheers, Tom 2016-03-18 23:18 GMT+01:00 Michael Theriot <michael.lee.theriot at gmail.com>: > I think I figured out how to make inheritance work... > > ```js > var wm1 = new WeakMap(); function A() { let proxy = new Proxy(this, { get: > (target, property, receiver) => property === 'bacon' || target[property] > }); wm1.set(proxy, {}); return proxy; } var wm2 = new WeakMap(); function > B() { let proxy = A.call(new Proxy(this, { get: (target, property, > receiver) => property === 'ham' || target[property] })); wm2.set(proxy, > {}); return proxy; } var a = new A(); var b = new B(); wm1.has(a); // true > wm2.has(a); // false wm1.has(b); // true wm2.has(b); // true > > a.bacon; // true > a.ham; // undefined > > b.bacon; // true > b.ham; // true > ``` > > I can't imagine this is good for optimizations though. But I guess it does > what I need. > > On Fri, Mar 18, 2016 at 4:45 PM, Michael Theriot < > michael.lee.theriot at gmail.com> wrote: > >> Michael’s preferred approach also introduces observable irregularity into >>> the standard JS inheritance model for ordinary objects. >>> Consider an object created using Michael’s preferred approach: >>> ```js >>> var arr = [0, 1]; >>> console.log(Reflect.has(arr,”0”)); //true, arr has “0” as an own >>> property >>> var subArr = Object.create(arr); >>> console.log(Reflect.has(subArr,”0”)); //true, all unshadowed properties >>> of proto visible from ordinary objects >>> var b = new ArrayView2(arr); >>> console.log(Reflect.has(b,”0”)); //true, prototype proxy makes array >>> elements visible as if properties of b >>> var subB= Object.create(b); >>> console.log(Reflect.has(subB,”0”)); //false, some unshadowed properties >>> of proto is not visible from subB >>> ``` >> >> >> I think this relates to the original concern; if you could pass the >> receiver this could be resolved. That still leaves `getOwnPropertyNames` >> reporting wrong values though and I see no foreseeable way to resolve that >> without returning an actual proxy itself. >> >> The reason I'm trying this approach is because I read on the MDN that >> when used in the prototype a `receiver` argument is passed that references >> the instance, so I assumed this was the intent behind it. The only other >> explanation I could think of is that proxies have a receiver to mimic the >> `Reflect.set`/`Reflect.get` methods which need a receiver for >> getters/setters to work properly, not so you can use them on the prototype >> chain. >> >> The other case I would make is every instance would have an identical >> proxy, and it just makes sense to put that on the prototype for the same >> reasons you put shared methods/properties there. >> >> Note that we are not really talking about a new capability here. >>> Michael’s first design shows that ES proxies already have the capability to >>> implement the object level semantics he desires. >> >> >> To be fair I had several obstacles with inheritance using the first >> version. >> >> ```js >> var wm1 = new WeakMap(); >> >> function A() { >> wm1.set(this, {}); >> return new Proxy(this, {}); >> } >> >> var wm2 = new WeakMap(); >> >> function B() { >> A.call(this); >> wm2.set(this, {}); >> return new Proxy(this, {}); >> } >> >> var a = new A(); >> var b = new B(); >> >> wm1.has(a); // true >> wm2.has(a); // false >> >> wm1.has(b); // false >> wm2.has(b); // true >> ``` >> >> As you can see storing a reference to `this` can't work anymore, since we >> actually return a proxy. You can try to work around this... >> >> ```js >> var wm1 = new WeakMap(); >> >> function A() { >> let self = this; >> if(new.target === A) { >> self = new Proxy(this, {}); >> } >> wm1.set(self, {}); >> return self; >> } >> >> var wm2 = new WeakMap(); >> >> function B() { >> let self = this; >> if(new.target === B) { >> self = new Proxy(this, {}); >> } >> A.call(self); >> wm2.set(self, {}); >> return self; >> } >> >> var a = new A(); >> var b = new B(); >> >> wm1.has(a); // true >> wm2.has(a); // false >> >> wm1.has(b); // true >> wm2.has(b); // true >> ``` >> >> But then problems arise because the new proxy doesn't go through the old >> proxy. So anything guaranteed by A()'s proxy is not guaranteed by B()'s >> proxy. >> >> ```js >> var wm1 = new WeakMap(); >> >> function A() { >> let self = this; >> if(new.target === A) { >> self = new Proxy(this, { >> get: (target, property, receiver) => property === 'bacon' || >> target[property] >> }); >> } >> wm1.set(self, {}); >> return self; >> } >> >> var wm2 = new WeakMap(); >> >> function B() { >> let self = this; >> if(new.target === B) { >> self = new Proxy(this, { >> get: (target, property, receiver) => property === 'ham' || >> target[property] >> }); >> } >> A.call(self); >> wm2.set(self, {}); >> return self; >> } >> >> var a = new A(); >> var b = new B(); >> >> wm1.has(a); // true >> wm2.has(a); // false >> >> wm1.has(b); // true >> wm2.has(b); // true >> >> a.bacon; // true >> a.ham; // undefined >> >> b.bacon; // undefined >> b.ham; // true >> ``` >> >> (I'm open to solutions on this particular case... One that doesn't >> require me to leak the handler of the A proxy) >> >> Ultimately I can actually achieve both what I want with the ArrayView >> example and inheritance by using a **lot** of `defineProperty` calls on >> `this` in the constructor, but performance is a disaster as you might >> expect. >> >> On Fri, Mar 18, 2016 at 2:55 PM, Andrea Giammarchi < >> andrea.giammarchi at gmail.com> wrote: >> >>> AFAIK the reason there is a `receiver` is to deal with prototype cases >>> ... if that was a good enough reason to have one, every prototype case >>> should be considered for consistency sake. >>> >>> We've been advocating prototypal inheritance for 20 years and now it's >>> an obstacle or "not how JS is"? >>> >>> ```js >>> class Magic extends new Proxy(unbe, lievable) { >>> // please make it happen >>> // as it is now, that won't work at all >>> } >>> ``` >>> >>> Best Regards >>> >>> >>> On Fri, Mar 18, 2016 at 7:30 PM, Mark S. Miller <erights at google.com> >>> wrote: >>> >>>> I agree with Allen. I am certainly willing -- often eager -- to revisit >>>> and revise old design decisions that are considered done, when I think the >>>> cost of leaving it alone exceeds the cost of changing it. In this case, the >>>> arguments that this extra parameter would be an improvement seem weak. Even >>>> without the revising-old-decision costs, I am uncertain which decision I >>>> would prefer. Given these costs, it seems clear we should leave this one >>>> alone. >>>> >>>> Unless it turns out that the cost of leaving it alone is much greater >>>> than I have understood. If so, please help me see what I'm missing. >>>> >>>> >>>> >>>> >>>> >>>> >>>> On Fri, Mar 18, 2016 at 12:17 PM, Allen Wirfs-Brock < >>>> allen at wirfs-brock.com> wrote: >>>> >>>>> >>>>> On Mar 18, 2016, at 9:24 AM, Andrea Giammarchi < >>>>> andrea.giammarchi at gmail.com> wrote: >>>>> >>>>> Agreed with everybody else the `receiver` is always needed and `Proxy` >>>>> on the prototype makes way more sense than per instance. >>>>> >>>>> >>>>> I don’t agree. While you certainly can imagine a language where each >>>>> object’s “prototype” determines that object’s fundamental behaviors and >>>>> provides the MOP intercession hooks(in fact that’s how most class-based >>>>> languages work). But that’s not the JS object model. Each JS object is >>>>> essentially a singleton that defines it’s own fundamental behaviors. >>>>> Whether or this model is better or worse than the class-based model isn't >>>>> really relevant, but in the context of JS there are advantage to >>>>> consistently adhering to that model, >>>>> >>>>> For example, in Michael’s desired approach, the instance objects of >>>>> his ArrayView abstraction are “ordinary objects”. One of the fundamental >>>>> behavioral characteristics of ordinary objects is that all of there own >>>>> properties are defined and available to the implementation in a standard >>>>> way. Implementations certainly make use of that characteristic for >>>>> optimization purposes. Michael’s approach would make such optimizations >>>>> invalid because every time an own property needed to be access a prototype >>>>> walk would have to be performed because there might be an exotic object >>>>> somewhere on the prototype chain that was injecting own property into the >>>>> original “receiver”. >>>>> >>>>> Michael’s preferred approach also introduces observable irregularity >>>>> into the standard JS inheritance model for ordinary objects. >>>>> >>>>> Consider an object created using Michael’s preferred approach: >>>>> >>>>> ```js >>>>> var arr = [0, 1]; >>>>> console.log(Reflect.has(arr,”0”)); //true, arr has “0” as an own >>>>> property >>>>> var subArr = Object.create(arr); >>>>> console.log(Reflect.has(subArr,”0”)); //true, all unshadowed >>>>> properties of proto visible from ordinary objects >>>>> >>>>> var b = new ArrayView2(arr); >>>>> console.log(Reflect.has(b,”0”)); //true, prototype proxy makes array >>>>> elements visible as if properties of b >>>>> var subB= Object.create(b); >>>>> console.log(Reflect.has(subB,”0”)); //false, some unshadowed >>>>> properties of proto is not visible from subB >>>>> ``` >>>>> >>>>> Note the his original Proxy implementation does not have this >>>>> undesirable characteristic. >>>>> >>>>> So what about the use of `receiver` in [[Get]]/[[Set]]. That’s a >>>>> different situation. [[Get]]/[[Set]] are not fundamental, rather they are >>>>> derived (they work by applying other more fundamental MOP operations). The >>>>> `receiver` argument is not used by them to perform property lookup (they >>>>> use [[GetOwnProperty]] and [[GetPrototypeOf]]) for the actual property >>>>> lookup). `receiver` is only used in the semantics of what happens after >>>>> the property lookup occurs. Adding a `receiver` argument to the other MOP >>>>> operations for the purpose of changing property lookup semantics seems like >>>>> a step too far. The ES MOP design is a balancing act between capability, >>>>> implementability, and consistency. I think adding `receiver` to every MOP >>>>> operation would throw the design out of balance. >>>>> >>>>> Finally, >>>>> >>>>> Note that we are not really talking about a new capability here. >>>>> Michael’s first design shows that ES proxies already have the capability to >>>>> implement the object level semantics he desires. So, we are only talking >>>>> about exactly how he goes about using Proxy to implement that semantics. He >>>>> would prefer a different Proxy design than what was actually provided by >>>>> ES6. But that isn’t what was specified or what has now been implemented. We >>>>> can all imagine how many JS features might be “better” if they worked >>>>> somewhat differently. But that generally isn’t an option. The existing >>>>> language features and their implementations are what they are and as JS >>>>> programmers we need to work within that reality. >>>>> >>>>> Allen >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> Also the `getPrototypeOf` trap is really pointless right now >>>>> >>>>> ```js >>>>> function Yak() {} >>>>> Yak.prototype = new Proxy(Yak.prototype, { >>>>> getPrototypeOf: (target) => console.log('lulz') >>>>> }); >>>>> >>>>> var yup = new Yak; >>>>> Object.getPrototypeOf(yup); >>>>> ``` >>>>> >>>>> The `target` is actually the original `Yak.prototype` which is already >>>>> the `yup` prototype: useless trap if used in such way. >>>>> >>>>> Being also unable to distinguish between `getOwnPropertyNames` vs >>>>> `keys` is a bit weird. >>>>> >>>>> `Proxy` looks so close to be that powerful but these bits make it >>>>> kinda useless for most real-world cases I've been recently dealing with. >>>>> >>>>> Thanks for any sort of improvement. >>>>> >>>>> Regards >>>>> >>>>> >>>>> >>>>> >>>>> On Fri, Mar 18, 2016 at 1:54 PM, Michael Theriot < >>>>> michael.lee.theriot at gmail.com> wrote: >>>>> >>>>>> 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 >>>>>>>> >>>>>>>> >>>>>>> >>>>>> >>>>>> _______________________________________________ >>>>>> es-discuss mailing list >>>>>> es-discuss at mozilla.org >>>>>> https://mail.mozilla.org/listinfo/es-discuss >>>>>> >>>>>> >>>>> _______________________________________________ >>>>> es-discuss mailing list >>>>> es-discuss at mozilla.org >>>>> https://mail.mozilla.org/listinfo/es-discuss >>>>> >>>>> >>>>> >>>>> _______________________________________________ >>>>> es-discuss mailing list >>>>> es-discuss at mozilla.org >>>>> https://mail.mozilla.org/listinfo/es-discuss >>>>> >>>>> >>>> >>>> >>>> -- >>>> Cheers, >>>> --MarkM >>>> >>> >>> >>> _______________________________________________ >>> es-discuss mailing list >>> es-discuss at mozilla.org >>> https://mail.mozilla.org/listinfo/es-discuss >>> >>> >> > > _______________________________________________ > 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/70e07cd1/attachment-0001.html>
To be clear, I'm not suggesting behavior like getOwnPropertyNames
be
overridden by anything on the prototype, just a way to use proxies without
having to instantiate identical copies that all use the same handler.
I still believe a proxy on the prototype should always have a receiver
sent to each trap, but if I need to return a proxy for each object it
doesn't really matter for the example I made.
To be clear, I'm not suggesting behavior like `getOwnPropertyNames` be overridden by anything on the prototype, just a way to use proxies without having to instantiate identical copies that all use the same handler. I still believe a proxy on the prototype should always have a `receiver` sent to each trap, but if I need to return a proxy for each object it doesn't really matter for the example I made. On Fri, Mar 18, 2016 at 5:43 PM, Tom Van Cutsem <tomvc.be at gmail.com> wrote: > I was on board with potentially making `has()` receiver-dependent but you > lost me when you expected `getOwnPropertyNames` to trigger a trap on a > proxy-as-prototype. Adding `receiver` to every MOP method is a no-go. It > fundamentally changes the meaning of these operations and it would destroy > the pay-only-when-you-use-it performance model of proxies, since operations > that used to be local to only the 'own' object would now need to search the > proto-chain for a potential proxy trap. > > Using a proxy-as-prototype was never intended as a way to be able to > intercept arbitrary operations on whatever object happened to inherit from > the proxy. A proxy-as-prototype still emulates only a single object. It > just happens to be an object that serves as a prototype for other objects. > > Cheers, > Tom > > 2016-03-18 23:18 GMT+01:00 Michael Theriot <michael.lee.theriot at gmail.com> > : > >> I think I figured out how to make inheritance work... >> >> ```js >> var wm1 = new WeakMap(); function A() { let proxy = new Proxy(this, { >> get: (target, property, receiver) => property === 'bacon' || >> target[property] }); wm1.set(proxy, {}); return proxy; } var wm2 = new >> WeakMap(); function B() { let proxy = A.call(new Proxy(this, { get: >> (target, property, receiver) => property === 'ham' || target[property] })); >> wm2.set(proxy, {}); return proxy; } var a = new A(); var b = new B(); >> wm1.has(a); // true wm2.has(a); // false wm1.has(b); // true wm2.has(b); // >> true >> >> a.bacon; // true >> a.ham; // undefined >> >> b.bacon; // true >> b.ham; // true >> ``` >> >> I can't imagine this is good for optimizations though. But I guess it >> does what I need. >> >> On Fri, Mar 18, 2016 at 4:45 PM, Michael Theriot < >> michael.lee.theriot at gmail.com> wrote: >> >>> Michael’s preferred approach also introduces observable irregularity >>>> into the standard JS inheritance model for ordinary objects. >>>> Consider an object created using Michael’s preferred approach: >>>> ```js >>>> var arr = [0, 1]; >>>> console.log(Reflect.has(arr,”0”)); //true, arr has “0” as an own >>>> property >>>> var subArr = Object.create(arr); >>>> console.log(Reflect.has(subArr,”0”)); //true, all unshadowed >>>> properties of proto visible from ordinary objects >>>> var b = new ArrayView2(arr); >>>> console.log(Reflect.has(b,”0”)); //true, prototype proxy makes array >>>> elements visible as if properties of b >>>> var subB= Object.create(b); >>>> console.log(Reflect.has(subB,”0”)); //false, some unshadowed >>>> properties of proto is not visible from subB >>>> ``` >>> >>> >>> I think this relates to the original concern; if you could pass the >>> receiver this could be resolved. That still leaves `getOwnPropertyNames` >>> reporting wrong values though and I see no foreseeable way to resolve that >>> without returning an actual proxy itself. >>> >>> The reason I'm trying this approach is because I read on the MDN that >>> when used in the prototype a `receiver` argument is passed that references >>> the instance, so I assumed this was the intent behind it. The only other >>> explanation I could think of is that proxies have a receiver to mimic the >>> `Reflect.set`/`Reflect.get` methods which need a receiver for >>> getters/setters to work properly, not so you can use them on the prototype >>> chain. >>> >>> The other case I would make is every instance would have an identical >>> proxy, and it just makes sense to put that on the prototype for the same >>> reasons you put shared methods/properties there. >>> >>> Note that we are not really talking about a new capability here. >>>> Michael’s first design shows that ES proxies already have the capability to >>>> implement the object level semantics he desires. >>> >>> >>> To be fair I had several obstacles with inheritance using the first >>> version. >>> >>> ```js >>> var wm1 = new WeakMap(); >>> >>> function A() { >>> wm1.set(this, {}); >>> return new Proxy(this, {}); >>> } >>> >>> var wm2 = new WeakMap(); >>> >>> function B() { >>> A.call(this); >>> wm2.set(this, {}); >>> return new Proxy(this, {}); >>> } >>> >>> var a = new A(); >>> var b = new B(); >>> >>> wm1.has(a); // true >>> wm2.has(a); // false >>> >>> wm1.has(b); // false >>> wm2.has(b); // true >>> ``` >>> >>> As you can see storing a reference to `this` can't work anymore, since >>> we actually return a proxy. You can try to work around this... >>> >>> ```js >>> var wm1 = new WeakMap(); >>> >>> function A() { >>> let self = this; >>> if(new.target === A) { >>> self = new Proxy(this, {}); >>> } >>> wm1.set(self, {}); >>> return self; >>> } >>> >>> var wm2 = new WeakMap(); >>> >>> function B() { >>> let self = this; >>> if(new.target === B) { >>> self = new Proxy(this, {}); >>> } >>> A.call(self); >>> wm2.set(self, {}); >>> return self; >>> } >>> >>> var a = new A(); >>> var b = new B(); >>> >>> wm1.has(a); // true >>> wm2.has(a); // false >>> >>> wm1.has(b); // true >>> wm2.has(b); // true >>> ``` >>> >>> But then problems arise because the new proxy doesn't go through the old >>> proxy. So anything guaranteed by A()'s proxy is not guaranteed by B()'s >>> proxy. >>> >>> ```js >>> var wm1 = new WeakMap(); >>> >>> function A() { >>> let self = this; >>> if(new.target === A) { >>> self = new Proxy(this, { >>> get: (target, property, receiver) => property === 'bacon' || >>> target[property] >>> }); >>> } >>> wm1.set(self, {}); >>> return self; >>> } >>> >>> var wm2 = new WeakMap(); >>> >>> function B() { >>> let self = this; >>> if(new.target === B) { >>> self = new Proxy(this, { >>> get: (target, property, receiver) => property === 'ham' || >>> target[property] >>> }); >>> } >>> A.call(self); >>> wm2.set(self, {}); >>> return self; >>> } >>> >>> var a = new A(); >>> var b = new B(); >>> >>> wm1.has(a); // true >>> wm2.has(a); // false >>> >>> wm1.has(b); // true >>> wm2.has(b); // true >>> >>> a.bacon; // true >>> a.ham; // undefined >>> >>> b.bacon; // undefined >>> b.ham; // true >>> ``` >>> >>> (I'm open to solutions on this particular case... One that doesn't >>> require me to leak the handler of the A proxy) >>> >>> Ultimately I can actually achieve both what I want with the ArrayView >>> example and inheritance by using a **lot** of `defineProperty` calls on >>> `this` in the constructor, but performance is a disaster as you might >>> expect. >>> >>> On Fri, Mar 18, 2016 at 2:55 PM, Andrea Giammarchi < >>> andrea.giammarchi at gmail.com> wrote: >>> >>>> AFAIK the reason there is a `receiver` is to deal with prototype cases >>>> ... if that was a good enough reason to have one, every prototype case >>>> should be considered for consistency sake. >>>> >>>> We've been advocating prototypal inheritance for 20 years and now it's >>>> an obstacle or "not how JS is"? >>>> >>>> ```js >>>> class Magic extends new Proxy(unbe, lievable) { >>>> // please make it happen >>>> // as it is now, that won't work at all >>>> } >>>> ``` >>>> >>>> Best Regards >>>> >>>> >>>> On Fri, Mar 18, 2016 at 7:30 PM, Mark S. Miller <erights at google.com> >>>> wrote: >>>> >>>>> I agree with Allen. I am certainly willing -- often eager -- to >>>>> revisit and revise old design decisions that are considered done, when I >>>>> think the cost of leaving it alone exceeds the cost of changing it. In this >>>>> case, the arguments that this extra parameter would be an improvement seem >>>>> weak. Even without the revising-old-decision costs, I am uncertain which >>>>> decision I would prefer. Given these costs, it seems clear we should leave >>>>> this one alone. >>>>> >>>>> Unless it turns out that the cost of leaving it alone is much greater >>>>> than I have understood. If so, please help me see what I'm missing. >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> On Fri, Mar 18, 2016 at 12:17 PM, Allen Wirfs-Brock < >>>>> allen at wirfs-brock.com> wrote: >>>>> >>>>>> >>>>>> On Mar 18, 2016, at 9:24 AM, Andrea Giammarchi < >>>>>> andrea.giammarchi at gmail.com> wrote: >>>>>> >>>>>> Agreed with everybody else the `receiver` is always needed and >>>>>> `Proxy` on the prototype makes way more sense than per instance. >>>>>> >>>>>> >>>>>> I don’t agree. While you certainly can imagine a language where each >>>>>> object’s “prototype” determines that object’s fundamental behaviors and >>>>>> provides the MOP intercession hooks(in fact that’s how most class-based >>>>>> languages work). But that’s not the JS object model. Each JS object is >>>>>> essentially a singleton that defines it’s own fundamental behaviors. >>>>>> Whether or this model is better or worse than the class-based model isn't >>>>>> really relevant, but in the context of JS there are advantage to >>>>>> consistently adhering to that model, >>>>>> >>>>>> For example, in Michael’s desired approach, the instance objects of >>>>>> his ArrayView abstraction are “ordinary objects”. One of the fundamental >>>>>> behavioral characteristics of ordinary objects is that all of there own >>>>>> properties are defined and available to the implementation in a standard >>>>>> way. Implementations certainly make use of that characteristic for >>>>>> optimization purposes. Michael’s approach would make such optimizations >>>>>> invalid because every time an own property needed to be access a prototype >>>>>> walk would have to be performed because there might be an exotic object >>>>>> somewhere on the prototype chain that was injecting own property into the >>>>>> original “receiver”. >>>>>> >>>>>> Michael’s preferred approach also introduces observable irregularity >>>>>> into the standard JS inheritance model for ordinary objects. >>>>>> >>>>>> Consider an object created using Michael’s preferred approach: >>>>>> >>>>>> ```js >>>>>> var arr = [0, 1]; >>>>>> console.log(Reflect.has(arr,”0”)); //true, arr has “0” as an own >>>>>> property >>>>>> var subArr = Object.create(arr); >>>>>> console.log(Reflect.has(subArr,”0”)); //true, all unshadowed >>>>>> properties of proto visible from ordinary objects >>>>>> >>>>>> var b = new ArrayView2(arr); >>>>>> console.log(Reflect.has(b,”0”)); //true, prototype proxy makes array >>>>>> elements visible as if properties of b >>>>>> var subB= Object.create(b); >>>>>> console.log(Reflect.has(subB,”0”)); //false, some unshadowed >>>>>> properties of proto is not visible from subB >>>>>> ``` >>>>>> >>>>>> Note the his original Proxy implementation does not have this >>>>>> undesirable characteristic. >>>>>> >>>>>> So what about the use of `receiver` in [[Get]]/[[Set]]. That’s a >>>>>> different situation. [[Get]]/[[Set]] are not fundamental, rather they are >>>>>> derived (they work by applying other more fundamental MOP operations). The >>>>>> `receiver` argument is not used by them to perform property lookup (they >>>>>> use [[GetOwnProperty]] and [[GetPrototypeOf]]) for the actual property >>>>>> lookup). `receiver` is only used in the semantics of what happens after >>>>>> the property lookup occurs. Adding a `receiver` argument to the other MOP >>>>>> operations for the purpose of changing property lookup semantics seems like >>>>>> a step too far. The ES MOP design is a balancing act between capability, >>>>>> implementability, and consistency. I think adding `receiver` to every MOP >>>>>> operation would throw the design out of balance. >>>>>> >>>>>> Finally, >>>>>> >>>>>> Note that we are not really talking about a new capability here. >>>>>> Michael’s first design shows that ES proxies already have the capability to >>>>>> implement the object level semantics he desires. So, we are only talking >>>>>> about exactly how he goes about using Proxy to implement that semantics. He >>>>>> would prefer a different Proxy design than what was actually provided by >>>>>> ES6. But that isn’t what was specified or what has now been implemented. We >>>>>> can all imagine how many JS features might be “better” if they worked >>>>>> somewhat differently. But that generally isn’t an option. The existing >>>>>> language features and their implementations are what they are and as JS >>>>>> programmers we need to work within that reality. >>>>>> >>>>>> Allen >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> Also the `getPrototypeOf` trap is really pointless right now >>>>>> >>>>>> ```js >>>>>> function Yak() {} >>>>>> Yak.prototype = new Proxy(Yak.prototype, { >>>>>> getPrototypeOf: (target) => console.log('lulz') >>>>>> }); >>>>>> >>>>>> var yup = new Yak; >>>>>> Object.getPrototypeOf(yup); >>>>>> ``` >>>>>> >>>>>> The `target` is actually the original `Yak.prototype` which is >>>>>> already the `yup` prototype: useless trap if used in such way. >>>>>> >>>>>> Being also unable to distinguish between `getOwnPropertyNames` vs >>>>>> `keys` is a bit weird. >>>>>> >>>>>> `Proxy` looks so close to be that powerful but these bits make it >>>>>> kinda useless for most real-world cases I've been recently dealing with. >>>>>> >>>>>> Thanks for any sort of improvement. >>>>>> >>>>>> Regards >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> On Fri, Mar 18, 2016 at 1:54 PM, Michael Theriot < >>>>>> michael.lee.theriot at gmail.com> wrote: >>>>>> >>>>>>> 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 >>>>>>>>> >>>>>>>>> >>>>>>>> >>>>>>> >>>>>>> _______________________________________________ >>>>>>> es-discuss mailing list >>>>>>> es-discuss at mozilla.org >>>>>>> https://mail.mozilla.org/listinfo/es-discuss >>>>>>> >>>>>>> >>>>>> _______________________________________________ >>>>>> es-discuss mailing list >>>>>> es-discuss at mozilla.org >>>>>> https://mail.mozilla.org/listinfo/es-discuss >>>>>> >>>>>> >>>>>> >>>>>> _______________________________________________ >>>>>> es-discuss mailing list >>>>>> es-discuss at mozilla.org >>>>>> https://mail.mozilla.org/listinfo/es-discuss >>>>>> >>>>>> >>>>> >>>>> >>>>> -- >>>>> Cheers, >>>>> --MarkM >>>>> >>>> >>>> >>>> _______________________________________________ >>>> es-discuss mailing list >>>> es-discuss at mozilla.org >>>> https://mail.mozilla.org/listinfo/es-discuss >>>> >>>> >>> >> >> _______________________________________________ >> 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/a5a6108b/attachment-0001.html>
2016-03-19 0:15 GMT+01:00 Michael Theriot <michael.lee.theriot at gmail.com>:
To be clear, I'm not suggesting behavior like
getOwnPropertyNames
be overridden by anything on the prototype, just a way to use proxies without having to instantiate identical copies that all use the same handler.
If you can already reuse the same handler object between multiple proxies, you're in good shape. There should be very little overhead in creating many proxy objects that share the same handler. A proxy object is just an empty object with a hidden reference to its handler.
Besides, if you want to reliably proxy multiple objects, you must instantiate a proxy per object anyway, otherwise you may get in trouble with object-identity. A single proxy object that proxies many different target objects would have only 1 object identity, so all the proxied targets would appear to be identity-equal.
2016-03-19 0:15 GMT+01:00 Michael Theriot <michael.lee.theriot at gmail.com>: > To be clear, I'm not suggesting behavior like `getOwnPropertyNames` be > overridden by anything on the prototype, just a way to use proxies without > having to instantiate identical copies that all use the same handler. > If you can already reuse the same handler object between multiple proxies, you're in good shape. There should be very little overhead in creating many proxy objects that share the same handler. A proxy object is just an empty object with a hidden reference to its handler. Besides, if you want to reliably proxy multiple objects, you must instantiate a proxy per object anyway, otherwise you may get in trouble with object-identity. A single proxy object that proxies many different target objects would have only 1 object identity, so all the proxied targets would appear to be identity-equal. Cheers, Tom -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160319/5c418226/attachment.html>
That is good news then. I think I have the right expectations of proxies now.
Sharing one handler is easy too. All you need to do is map both the proxy
and its target
to the same data. receiver
is actually the proxy but the
argument is no longer important here.
var priv = new WeakMap();
var handler = {
get: (target, property, receiver) => property in priv.get(target)
? priv.get(target)[property]
: target[property],
set: (target, property, value, receiver) => property in priv.get(target)
? priv.get(target)[property] = value
: target[property] = value,
has: (target, property) => property in priv.get(target) || property in target
};
function A() {
let proxy = new Proxy(this, handler);
let store = {secret: 4};
priv.set(this, store).set(proxy, store);
return proxy;
}
A.prototype.getSecret = function () {
return priv.get(this).secret;
};
var a = new A();
a.getSecret(); // 4
a.secret; // 4
a.secret = 5;
a.secret; // 5
a.getSecret(); // 5
'secret' in a; // true
(sorry for any dupes, new to mailing lists...)
That is good news then. I think I have the right expectations of proxies now. Sharing one handler is easy too. All you need to do is map both the `proxy` and its `target` to the same data. `receiver` is actually the proxy but the argument is no longer important here. ```js var priv = new WeakMap(); var handler = { get: (target, property, receiver) => property in priv.get(target) ? priv.get(target)[property] : target[property], set: (target, property, value, receiver) => property in priv.get(target) ? priv.get(target)[property] = value : target[property] = value, has: (target, property) => property in priv.get(target) || property in target }; function A() { let proxy = new Proxy(this, handler); let store = {secret: 4}; priv.set(this, store).set(proxy, store); return proxy; } A.prototype.getSecret = function () { return priv.get(this).secret; }; var a = new A(); a.getSecret(); // 4 a.secret; // 4 a.secret = 5; a.secret; // 5 a.getSecret(); // 5 'secret' in a; // true ``` (sorry for any dupes, new to mailing lists...) On Thu, Mar 17, 2016 at 5:46 AM, Michael Theriot < michael.lee.theriot at gmail.com> wrote: > 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/20160403/d7278e9b/attachment.html>
I really thing that because has
is about detecting inherited
properties, the receiver
parameter should be included. For things
like ownKeys
, which are not about inheritance, then yeah, let's not
add receiver there.
I'm trying to implement my own multiple inheritance, but now I
stumbled on how to make it send back true for inherited keys when I
need to fork the lookup based on instances that are WeakMap
ed to the
receiver
.
I really thing that because `has` is about detecting inherited properties, the `receiver` parameter should be included. For things like `ownKeys`, which are not about inheritance, then yeah, let's not add receiver there. I'm trying to implement my own multiple inheritance, but now I stumbled on how to make it send back true for inherited keys when I need to fork the lookup based on instances that are `WeakMap`ed to the `receiver`.
After messing with Proxy-on-prototypes for two days, I've just come to the conclusion that I probably need to have Proxies on this (the receiver) returned from constructors to achieve what I want. At least, it's much easier to code it that way. I think it'd be nice to have receiver on all inheritance-related traps. That might make some things easier.
After messing with Proxy-on-prototypes for two days, I've just come to the conclusion that I probably need to have Proxies on this (the receiver) returned from constructors to achieve what I want. At least, it's much easier to code it that way. I think it'd be nice to have receiver on all inheritance-related traps. That might make some things easier. On Thu, Nov 21, 2019 at 2:55 PM #!/JoePea <joe at trusktr.io> wrote: > > I really thing that because `has` is about detecting inherited > properties, the `receiver` parameter should be included. For things > like `ownKeys`, which are not about inheritance, then yeah, let's not > add receiver there. > > I'm trying to implement my own multiple inheritance, but now I > stumbled on how to make it send back true for inherited keys when I > need to fork the lookup based on instances that are `WeakMap`ed to the > `receiver`.
It's not answering your issue with Proxy but more about multiple inheritance
It can be solved in a static way: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes#Mix-ins
Concrete example here: pepabo/gmopg/blob/master/src/gmopg.ts#L10
It's not answering your issue with Proxy but more about multiple inheritance It can be solved in a static way: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes#Mix-ins Concrete example here: https://github.com/pepabo/gmopg/blob/master/src/gmopg.ts#L10 On Fri, Nov 22, 2019 at 4:23 AM #!/JoePea <joe at trusktr.io> wrote: > After messing with Proxy-on-prototypes for two days, I've just come to > the conclusion that I probably need to have Proxies on this (the > receiver) returned from constructors to achieve what I want. At least, > it's much easier to code it that way. I think it'd be nice to have > receiver on all inheritance-related traps. That might make some things > easier. > > On Thu, Nov 21, 2019 at 2:55 PM #!/JoePea <joe at trusktr.io> wrote: > > > > I really thing that because `has` is about detecting inherited > > properties, the `receiver` parameter should be included. For things > > like `ownKeys`, which are not about inheritance, then yeah, let's not > > add receiver there. > > > > I'm trying to implement my own multiple inheritance, but now I > > stumbled on how to make it send back true for inherited keys when I > > need to fork the lookup based on instances that are `WeakMap`ed to the > > `receiver`. > _______________________________________________ > 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/20191122/6a86fc57/attachment.html>
thanks for pointing that out! I know about those, I've been using class-factory mixins for a while now. But the problem with them is having to wrap all your classes in a function, which gets much more painful in TypeScript with all the type-annotation boilerplate that is required.
For example, here's a boilerplate-heavy mixin of mine:
- Header: infamous/infamous/blob/develop/src/core/ImperativeBase.ts#L55
- Footer: infamous/infamous/blob/develop/src/core/ImperativeBase.ts#L615
- Mixin helper application (provides mixin result caching, deduplication, Symbol.hasInstance, etc): infamous/infamous/blob/develop/src/core/ImperativeBase.ts#L662
- The type system that I imported from my lowclass lib: infamous/infamous/blob/develop/src/core/ImperativeBase.ts#L2
Multiple inheritance with Proxies actually turns out very easy to type in TypeScript, and a lot more convenient than class factories. So, given a set of regular classes,
class One {
doOne() { /* ... */ }
}
class Two {
doTwo() { /* ... */ }
}
class Three {
doThree() { /* ... */ }
}
a Proxy-based multiple inheritance system makes the composition super easy, clean, and simple. We can convert the previous example into:
import mix from './mix'
class One {
doOne() { /* ... */ }
}
class Two {
doTwo() { /* ... */ }
}
class Three extends mix(One, Two) {
doThree() { /* ... */ }
doAll() {
this.doOne()
this.doTwo()
this.doThree()
}
}
All without touching the original source of the One
and Two
classes.
How convenient, and easier to read and look at!
My particular implementation will allow to call individual constructors with specific args, unlike the class-factory mixins. For example,
import mix from './mix'
class One {
constructor(arg1, arg2) {/*...*/}
doOne() { /* ... */ }
}
class Two {
constructor(arg3) {/*...*/}
doTwo() { /* ... */ }
}
class Three extends mix(One, Two) {
constructor(arg1, arg2, arg3) {
this.callConstructor(One, arg1, arg2)
this.callConstructor(Two, arg3)
}
doThree() { /* ... */ }
doAll() {
this.doOne()
this.doTwo()
this.doThree()
}
}
At the moment I'm looking to convert from my Proxies-on-prototypes
implementation to Proxies-on-instances so that things like in
operator
will work, and implementation will be simpler and easier. Otherwise if the
has
trap had a receiver
parameter, then I could stick with the
Proxies-on-prototypes version which would be more efficient (the Proxy
would be re-used on the prototype chain instead of being created once per
instance).
Would it be worth adding a receiver
parameter to the has
trap in the
specs? Seems like it would be totally backwards compatible, because current
code that doesn't rely on it could continue not relying on it oblivious to
the new parameter.
All the best,
HI Cyril, thanks for pointing that out! I know about those, I've been using class-factory mixins for a while now. But the problem with them is having to wrap all your classes in a function, which gets much more painful in TypeScript with all the type-annotation boilerplate that is required. For example, here's a boilerplate-heavy mixin of mine: - Header: https://github.com/infamous/infamous/blob/develop/src/core/ImperativeBase.ts#L55 - Footer: https://github.com/infamous/infamous/blob/develop/src/core/ImperativeBase.ts#L615 - Mixin helper application (provides mixin result caching, deduplication, Symbol.hasInstance, etc): https://github.com/infamous/infamous/blob/develop/src/core/ImperativeBase.ts#L662 - The type system that I imported from my lowclass lib: https://github.com/infamous/infamous/blob/develop/src/core/ImperativeBase.ts#L2 Multiple inheritance with Proxies actually turns out very easy to type in TypeScript, and a lot more convenient than class factories. So, given a set of regular classes, ```js class One { doOne() { /* ... */ } } class Two { doTwo() { /* ... */ } } class Three { doThree() { /* ... */ } } ``` a Proxy-based multiple inheritance system makes the composition super easy, clean, and simple. We can convert the previous example into: ```js import mix from './mix' class One { doOne() { /* ... */ } } class Two { doTwo() { /* ... */ } } class Three extends mix(One, Two) { doThree() { /* ... */ } doAll() { this.doOne() this.doTwo() this.doThree() } } ``` All without touching the original source of the `One` and `Two` classes. How convenient, and easier to read and look at! My particular implementation will allow to call individual constructors with specific args, unlike the class-factory mixins. For example, ```js import mix from './mix' class One { constructor(arg1, arg2) {/*...*/} doOne() { /* ... */ } } class Two { constructor(arg3) {/*...*/} doTwo() { /* ... */ } } class Three extends mix(One, Two) { constructor(arg1, arg2, arg3) { this.callConstructor(One, arg1, arg2) this.callConstructor(Two, arg3) } doThree() { /* ... */ } doAll() { this.doOne() this.doTwo() this.doThree() } } ``` At the moment I'm looking to convert from my Proxies-on-prototypes implementation to Proxies-on-instances so that things like `in` operator will work, and implementation will be simpler and easier. Otherwise if the `has` trap had a `receiver` parameter, then I could stick with the Proxies-on-prototypes version which would be more efficient (the Proxy would be re-used on the prototype chain instead of being created once per instance). Would it be worth adding a `receiver` parameter to the `has` trap in the specs? Seems like it would be totally backwards compatible, because current code that doesn't rely on it could continue not relying on it oblivious to the new parameter. All the best, - Joe On Fri, Nov 22, 2019 at 3:55 AM Cyril Auburtin <cyril.auburtin at gmail.com> wrote: > It's not answering your issue with Proxy but more about multiple > inheritance > > It can be solved in a static way: > https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes#Mix-ins > > Concrete example here: > https://github.com/pepabo/gmopg/blob/master/src/gmopg.ts#L10 > > On Fri, Nov 22, 2019 at 4:23 AM #!/JoePea <joe at trusktr.io> wrote: > >> After messing with Proxy-on-prototypes for two days, I've just come to >> the conclusion that I probably need to have Proxies on this (the >> receiver) returned from constructors to achieve what I want. At least, >> it's much easier to code it that way. I think it'd be nice to have >> receiver on all inheritance-related traps. That might make some things >> easier. >> >> On Thu, Nov 21, 2019 at 2:55 PM #!/JoePea <joe at trusktr.io> wrote: >> > >> > I really thing that because `has` is about detecting inherited >> > properties, the `receiver` parameter should be included. For things >> > like `ownKeys`, which are not about inheritance, then yeah, let's not >> > add receiver there. >> > >> > I'm trying to implement my own multiple inheritance, but now I >> > stumbled on how to make it send back true for inherited keys when I >> > need to fork the lookup based on instances that are `WeakMap`ed to the >> > `receiver`. >> _______________________________________________ >> 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/20191122/a6f66fdc/attachment-0001.html>
I forgot to add, this even works with builtin scenarios like Custom Elements,
class MyEl extends mix(HTMLButtonElement, One, Two, Three) { /* ... */ }
customElements.define('my-el', MyEl)
I forgot to add, this even works with builtin scenarios like Custom Elements, ```js class MyEl extends mix(HTMLButtonElement, One, Two, Three) { /* ... */ } customElements.define('my-el', MyEl) ``` On Fri, Nov 22, 2019 at 9:36 AM #!/JoePea <joe at trusktr.io> wrote: > HI Cyril, thanks for pointing that out! I know about those, I've been > using class-factory mixins for a while now. But the problem with them is > having to wrap all your classes in a function, which gets much more painful > in TypeScript with all the type-annotation boilerplate that is required. > > For example, here's a boilerplate-heavy mixin of mine: > > - Header: > https://github.com/infamous/infamous/blob/develop/src/core/ImperativeBase.ts#L55 > - Footer: > https://github.com/infamous/infamous/blob/develop/src/core/ImperativeBase.ts#L615 > - Mixin helper application (provides mixin result caching, deduplication, > Symbol.hasInstance, etc): > https://github.com/infamous/infamous/blob/develop/src/core/ImperativeBase.ts#L662 > - The type system that I imported from my lowclass lib: > https://github.com/infamous/infamous/blob/develop/src/core/ImperativeBase.ts#L2 > > Multiple inheritance with Proxies actually turns out very easy to type in > TypeScript, and a lot more convenient than class factories. So, given a set > of regular classes, > > ```js > class One { > doOne() { /* ... */ } > } > > class Two { > doTwo() { /* ... */ } > } > > class Three { > doThree() { /* ... */ } > } > ``` > > a Proxy-based multiple inheritance system makes the composition super > easy, clean, and simple. We can convert the previous example into: > > ```js > import mix from './mix' > > class One { > doOne() { /* ... */ } > } > > class Two { > doTwo() { /* ... */ } > } > > class Three extends mix(One, Two) { > doThree() { /* ... */ } > doAll() { > this.doOne() > this.doTwo() > this.doThree() > } > } > ``` > > All without touching the original source of the `One` and `Two` classes. > How convenient, and easier to read and look at! > > My particular implementation will allow to call individual constructors > with specific args, unlike the class-factory mixins. For example, > > ```js > import mix from './mix' > > class One { > constructor(arg1, arg2) {/*...*/} > doOne() { /* ... */ } > } > > class Two { > constructor(arg3) {/*...*/} > doTwo() { /* ... */ } > } > > class Three extends mix(One, Two) { > constructor(arg1, arg2, arg3) { > this.callConstructor(One, arg1, arg2) > this.callConstructor(Two, arg3) > } > doThree() { /* ... */ } > doAll() { > this.doOne() > this.doTwo() > this.doThree() > } > } > ``` > > At the moment I'm looking to convert from my Proxies-on-prototypes > implementation to Proxies-on-instances so that things like `in` operator > will work, and implementation will be simpler and easier. Otherwise if the > `has` trap had a `receiver` parameter, then I could stick with the > Proxies-on-prototypes version which would be more efficient (the Proxy > would be re-used on the prototype chain instead of being created once per > instance). > > Would it be worth adding a `receiver` parameter to the `has` trap in the > specs? Seems like it would be totally backwards compatible, because current > code that doesn't rely on it could continue not relying on it oblivious to > the new parameter. > > All the best, > - Joe > > On Fri, Nov 22, 2019 at 3:55 AM Cyril Auburtin <cyril.auburtin at gmail.com> > wrote: > >> It's not answering your issue with Proxy but more about multiple >> inheritance >> >> It can be solved in a static way: >> https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes#Mix-ins >> >> Concrete example here: >> https://github.com/pepabo/gmopg/blob/master/src/gmopg.ts#L10 >> >> On Fri, Nov 22, 2019 at 4:23 AM #!/JoePea <joe at trusktr.io> wrote: >> >>> After messing with Proxy-on-prototypes for two days, I've just come to >>> the conclusion that I probably need to have Proxies on this (the >>> receiver) returned from constructors to achieve what I want. At least, >>> it's much easier to code it that way. I think it'd be nice to have >>> receiver on all inheritance-related traps. That might make some things >>> easier. >>> >>> On Thu, Nov 21, 2019 at 2:55 PM #!/JoePea <joe at trusktr.io> wrote: >>> > >>> > I really thing that because `has` is about detecting inherited >>> > properties, the `receiver` parameter should be included. For things >>> > like `ownKeys`, which are not about inheritance, then yeah, let's not >>> > add receiver there. >>> > >>> > I'm trying to implement my own multiple inheritance, but now I >>> > stumbled on how to make it send back true for inherited keys when I >>> > need to fork the lookup based on instances that are `WeakMap`ed to the >>> > `receiver`. >>> _______________________________________________ >>> 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/20191122/01c6d6da/attachment.html>
I feel like it should, or I am misunderstanding something fundamental. I made a basic scenario to explain:
var arrays = new WeakMap(); function ArrayView(array) { arrays.set(this, array); return new Proxy(this, { set: (target, property, value) => { if(arrays.has(this) && property in arrays.get(this)) { arrays.get(this)[property] = value; } else { target[property] = value; } }, get: (target, property) => { if(arrays.has(this) && property in arrays.get(this)) { return arrays.get(this)[property]; } else { return target[property]; } }, has: (target, property) => { return (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 tosomearray
is stored in thearrays
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: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) => { if(arrays.has(receiver) && property in arrays.get(receiver)) { arrays.get(receiver)[property] = value; } else { Reflect.set(target, property, value, receiver); } }, get: (target, property, receiver) => { if(arrays.has(receiver) && property in arrays.get(receiver)) { return arrays.get(receiver)[property]; } else { return Reflect.get(target, property, receiver); } }, has: (target, property) => { return Reflect.has(target, property); // no receiver to check weak map!! } });
Under this setup
target
refers to the protoLayer object which is useless here, but we can use thereceiver
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 correctthis
value to thearrayLength
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.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 [...a]; // [0, 1] a[0] = 1; a[1] = 0; [...a]; // [1, 0] var b = new ArrayView2(arr); b.arrayLength; // 2 'arrayLength' in b; // true '0' in b; // false '1' in b; // false '2' in b; // false // we can still get/set indices despite 'in' check failing [...b]; // [1, 0] b[0] = 0; b[1] = 1; [...b]; // [0, 1]
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.