Why does a JavaScript class getter for a private field fail using a Proxy?

kai zhu kaizhu256 at gmail.com
Sun Jul 12 21:45:40 UTC 2020


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 <
michael.lee.theriot at gmail.com> 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 <
> michael.lee.theriot at gmail.com> 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 <
>> francois.remy.dev at outlook.com> 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 <michael.lee.theriot at gmail.com>
>>> *Sent: *Sunday, July 12, 2020 20:59
>>> *To: *Michael Haufe <tno at thenewobjective.com>
>>> *Cc: *es-discuss at mozilla.org
>>> *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 <tno at thenewobjective.com>
>>> 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 <es-discuss-bounces at mozilla.org> *On Behalf Of *Laurie
>>> Harper
>>> *Sent:* Friday, June 5, 2020 12:21 AM
>>> *To:* es-discuss at mozilla.org
>>> *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
>>> 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/20200712/2d7eede9/attachment-0001.html>


More information about the es-discuss mailing list