>  private members (safely) allow classes with internal slots.

I'd say that they aren't safe, if they can break 3rd-party code on the
external public side.

#!/JoePea

On Sun, Jul 12, 2020 at 3:09 PM Michael Theriot
<[email protected]> wrote:
>
> I assume OP wants to use proxies and private members together. They are not 
> designed to be compatible.
>
> Proxies and private members are a UX goal primarily for developers. Proxies 
> easily allow observation of another object or creation of exotic objects 
> (e.g. Array), and private members (safely) allow classes with internal slots. 
> Since they cannot be used together the issue exists, and the hack circumvents 
> this by reimplementing private in a way that does not require private fields.
>
> On Sun, Jul 12, 2020 at 4:45 PM kai zhu <[email protected]> wrote:
>>
>> as product-developer, can i ask what ux-objective you ultimately want 
>> achieved?
>>
>> ```js
>> const sub = new Sub()
>>
>> // i'm a noob on proxies. what is this thing (with proxied-private-fields) 
>> ultimately used for?
>> const proxy = new Proxy(sub, ...)
>> ```
>>
>> On Sun, Jul 12, 2020 at 4:34 PM Michael Theriot 
>> <[email protected]> wrote:
>>>
>>> This does require you to have both the key and the weakmap though, so it 
>>> actually does succeed in hiding the data so long as the weakmap is out of 
>>> scope. I guess the issue I can foresee is that the key could be modified 
>>> after the object is created.
>>>
>>> e.g.
>>> ```js
>>> var a = new A();
>>> var key = Object.getOwnPropertySymbols(a)[0];
>>> delete a[key];
>>> a.hidden; // throws
>>> ```
>>>
>>> That itself can be guarded by just making the key undeletable. So, I guess 
>>> this solution could work depending what your goals are?
>>>
>>> On Sun, Jul 12, 2020 at 4:21 PM Michael Theriot 
>>> <[email protected]> wrote:
>>>>
>>>> It nearly works, but the issue is that the key will be leaked by 
>>>> `Object.getOwnPropertySymbols(new A())`, so it's not truly private.
>>>>
>>>> There have been ideas proposing "private symbols" but I am not familiar 
>>>> with their issues, and I would guess with Class Fields they are unlikely 
>>>> to materialize anyway.
>>>>
>>>> On Sun, Jul 12, 2020 at 2:19 PM François REMY 
>>>> <[email protected]> wrote:
>>>>>
>>>>> At the risk of pointing out the obvious:
>>>>>
>>>>>
>>>>>
>>>>> ```js
>>>>>
>>>>> const privkey = Symbol();
>>>>>
>>>>> const stores = new WeakMap();
>>>>>
>>>>>
>>>>>
>>>>> class A {
>>>>>
>>>>>   [privkey] = {};
>>>>>
>>>>>   constructor() {
>>>>>
>>>>>     const priv = {};
>>>>>
>>>>>     priv.hidden = Math.random();
>>>>>
>>>>>     stores.set(this[privkey], priv);
>>>>>
>>>>>   }
>>>>>
>>>>>
>>>>>
>>>>>   get hidden() {
>>>>>
>>>>>     const priv = stores.get(this[privkey]);
>>>>>
>>>>>     return priv.hidden;
>>>>>
>>>>>   }
>>>>>
>>>>> }
>>>>>
>>>>>
>>>>>
>>>>> var as = [
>>>>>
>>>>>                 new A(),
>>>>>
>>>>>                 new Proxy(new A(),{}),
>>>>>
>>>>>                 new Proxy(new A(),{}),
>>>>>
>>>>> ];
>>>>>
>>>>> console.log(as.map(a=>a.hidden));
>>>>>
>>>>> ```
>>>>>
>>>>>
>>>>>
>>>>>
>>>>>
>>>>>
>>>>>
>>>>> From: Michael Theriot
>>>>> Sent: Sunday, July 12, 2020 20:59
>>>>> To: Michael Haufe
>>>>> Cc: [email protected]
>>>>> Subject: Re: Why does a JavaScript class getter for a private field fail 
>>>>> using a Proxy?
>>>>>
>>>>>
>>>>>
>>>>> I experienced this issue prior to this proposal, using weakmaps for 
>>>>> private access.
>>>>>
>>>>>
>>>>>
>>>>> e.g.
>>>>>
>>>>> ```js
>>>>>
>>>>> const stores = new WeakMap();
>>>>>
>>>>> class A {
>>>>>   constructor() {
>>>>>     const priv = {};
>>>>>     priv.hidden = 0;
>>>>>     stores.set(this, priv);
>>>>>   }
>>>>>
>>>>>   get hidden() {
>>>>>     const priv = stores.get(this);
>>>>>     return priv.hidden;
>>>>>   }
>>>>> }
>>>>>
>>>>> const a = new A();
>>>>> console.log(a.hidden); // 0
>>>>>
>>>>> const p = new Proxy(a, {});
>>>>> console.log(p.hidden); // throws!
>>>>>
>>>>> ```
>>>>>
>>>>>
>>>>>
>>>>> I found a workaround:
>>>>>
>>>>>
>>>>>
>>>>> ```js
>>>>> const stores = new WeakMap();
>>>>>
>>>>> class A {
>>>>>   constructor() {
>>>>>     const priv = {};
>>>>>     priv.hidden = 0;
>>>>>     stores.set(this, priv);
>>>>>
>>>>>     const p = new Proxy(this, {});
>>>>>     stores.set(p, priv); // set proxy to map to the same private store
>>>>>
>>>>>     return p;
>>>>>   }
>>>>>
>>>>>   get hidden() {
>>>>>     const priv = stores.get(this); // the original instance and proxy 
>>>>> both map to the same private store now
>>>>>     return priv.hidden;
>>>>>   }
>>>>> }
>>>>>
>>>>> const a = new A();
>>>>>
>>>>> console.log(a.hidden);
>>>>> ```
>>>>>
>>>>>
>>>>>
>>>>> Not ideal, and only works if you provide the proxy in the first place 
>>>>> (e.g. making exotic JS objects). But, not necessarily a new issue with 
>>>>> proxies, either.
>>>>>
>>>>>
>>>>>
>>>>> On Fri, Jun 5, 2020 at 12:29 AM Michael Haufe <[email protected]> 
>>>>> wrote:
>>>>>
>>>>> This is a known issue and very painful for me as well. You can see a long 
>>>>> ugly discussion here:
>>>>>
>>>>>
>>>>>
>>>>> <https://github.com/tc39/proposal-class-fields/issues/106>
>>>>>
>>>>>
>>>>>
>>>>> I suggest the following guide to assist you:
>>>>>
>>>>>
>>>>>
>>>>> <https://javascript.info/proxy#proxy-limitations>
>>>>>
>>>>>
>>>>>
>>>>> Another possible approach is to have your classes extend a proxy:
>>>>>
>>>>>
>>>>>
>>>>> <https://github.com/tc39/proposal-class-fields/issues/106#issuecomment-397484713>
>>>>>
>>>>>
>>>>>
>>>>>
>>>>>
>>>>> From: es-discuss <[email protected]> On Behalf Of Laurie 
>>>>> Harper
>>>>> Sent: Friday, June 5, 2020 12:21 AM
>>>>> To: [email protected]
>>>>> Subject: Why does a JavaScript class getter for a private field fail 
>>>>> using a Proxy?
>>>>>
>>>>>
>>>>>
>>>>> I can expose private class fields in JavaScript using getters, and those 
>>>>> getters work correctly when invoked on instances of a subclass. However, 
>>>>> if I then wrap the instance with a proxy the getter will throw a type 
>>>>> error, even if the proxy `get` hook uses `Reflect.get()`:
>>>>>
>>>>> ```
>>>>> class Base {
>>>>>     _attrA
>>>>>     #_attrB
>>>>>
>>>>>     constructor() {
>>>>>         this._attrA = 100
>>>>>         this.#_attrB = 200
>>>>>     }
>>>>>
>>>>>     get A() { return this._attrA }
>>>>>
>>>>>     get B() { return this.#_attrB }
>>>>>
>>>>>     incrA() { this._attrA++ }
>>>>>
>>>>>     incrB() { this.#_attrB++ }
>>>>> }
>>>>>
>>>>> class Sub extends Base {}
>>>>>
>>>>> const sub = new Sub()
>>>>>
>>>>> const proxy = new Proxy(sub, {
>>>>>     get(target, prop, receiver) {
>>>>>         const value = Reflect.get(target, prop, receiver)
>>>>>         return typeof value === 'function' ? value.bind(target) : value 
>>>>> // (1)
>>>>>     }
>>>>> })
>>>>>
>>>>> console.log('sub.A', sub.A) // OK: -> 100
>>>>> console.log('sub.B', sub.B) // OK: -> 200
>>>>> sub.incrA() // OK
>>>>> sub.incrB() // OK
>>>>> console.log('sub.A', sub.A) // OK: -> 101
>>>>> console.log('sub.B', sub.B) // OK: -> 201
>>>>>
>>>>> console.log('proxy.A', proxy.A) // OK: -> 100
>>>>> console.log('proxy.B', proxy.B) // TypeError: Cannot read private member 
>>>>> #_attrB from an object whose class did not declare it
>>>>> proxy.incrA() // OK
>>>>> proxy.incrB() // OK due to (1)
>>>>> console.log('proxy.A', proxy.A) // OK: -> 100
>>>>> console.log('proxy.B', proxy.B) // TypeError: Cannot read private member 
>>>>> #_attrB from an object whose class did not declare it
>>>>> ```
>>>>>
>>>>> The call to `proxy.incrB()` works, because the proxy handler explicitly 
>>>>> binds function values to `target` on line (1). Without the `bind()` call, 
>>>>> the `proxy.incrB()` invocation would throw a `TypeError` like the getter 
>>>>> invocation does. That makes some sense: the result of the call to 
>>>>> `Reflect.get()` is the 'unbound' function value of the property being 
>>>>> retrieved, which must then be bound to `target`; it would make more 
>>>>> sense, though, if `this` binding was applied by the [[Call]] operation on 
>>>>> the result of the [[Get]] operation...
>>>>>
>>>>> But there is no opportunity to 'bind' a getter before invoking it; as a 
>>>>> result, a proxied getter ends up receiving the wrong `this` binding, 
>>>>> leading to the inconsistency.
>>>>>
>>>>> Is there any way to make this work correctly? The only approach I can 
>>>>> think of (which I haven't tried) would be to have the `get` hook walk up 
>>>>> the prototype chain, starting from `target`, calling 
>>>>> `getOwnPropertyDescriptor()` and checking for a getter method, and 
>>>>> explicitly applying the getter with an adjusted `this` binding. That 
>>>>> sounds ludicrously cumbersome and brittle...
>>>>>
>>>>> Is there a better way to get this working correctly?
>>>>>
>>>>>
>>>>>
>>>>> --
>>>>>
>>>>> Laurie
>>>>>
>>>>> _______________________________________________
>>>>> es-discuss mailing list
>>>>> [email protected]
>>>>> https://mail.mozilla.org/listinfo/es-discuss
>>>>>
>>>>>
>>>
>>> _______________________________________________
>>> es-discuss mailing list
>>> [email protected]
>>> https://mail.mozilla.org/listinfo/es-discuss
>
> _______________________________________________
> es-discuss mailing list
> [email protected]
> https://mail.mozilla.org/listinfo/es-discuss
_______________________________________________
es-discuss mailing list
[email protected]
https://mail.mozilla.org/listinfo/es-discuss

Reply via email to