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

Michael Haufe tno at thenewobjective.com
Fri Jun 5 05:28:52 UTC 2020


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
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20200605/34bd7ac2/attachment-0001.html>


More information about the es-discuss mailing list