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....@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.ther...@gmail.com> > *Sent: *Sunday, July 12, 2020 20:59 > *To: *Michael Haufe <t...@thenewobjective.com> > *Cc: *es-discuss@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 <t...@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-boun...@mozilla.org> *On Behalf Of *Laurie > Harper > *Sent:* Friday, June 5, 2020 12:21 AM > *To:* es-discuss@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@mozilla.org > https://mail.mozilla.org/listinfo/es-discuss > > >
_______________________________________________ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss