ES6 proxied class, access private property (Cannot read private member #hidden from an object whose class did not declare it)

Viewed 2213

Im playing around with proxy objects, classess and private properties. And came across this error message:

/home/marc/projects/playground/pipeline/clsss.js:14
        this.#hidden = !this.#hidden;
                             ^

TypeError: Cannot read private member #hidden from an object whose class did not declare it
    at Proxy.toggle (/home/marc/projects/playground/pipeline/clsss.js:14:30)
    at Object.<anonymous> (/home/marc/projects/playground/pipeline/clsss.js:37:19)
    at Module._compile (internal/modules/cjs/loader.js:1118:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1138:10)
    at Module.load (internal/modules/cjs/loader.js:982:32)
    at Function.Module._load (internal/modules/cjs/loader.js:875:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
    at internal/main/run_main_module.js:17:47

Code to reproduce:

class Parent {

    #hidden;

    constructor() {
        this.#hidden = false;
    }

    get hidden() {
        return this.#hidden;
    }

    toggle() {
        this.#hidden = !this.#hidden;
        console.log("Changed", this.#hidden)
        return this.#hidden;
    }

}


const p = new Parent();
const proxy = new Proxy(p, {
    get: (target, prop, receiver) => {
        return target[prop];
    }
});

console.log(p.toggle())
console.log(proxy.toggle())  // this is the problem
console.log(p.toggle())

Is there a way to handle private properties on proxied class instance?

Why does this not work with a proxy?

Thanks for any hint/answer.

Edit: found a related issue on github: https://github.com/tc39/proposal-class-fields/issues/106 A quick hack i discoverd is to use:

const proxy = new Proxy(..., {
    get: (target, prop, receiver) => {

        // bind context to original object
        if (target[prop] instanceof Function) {
            return target[prop].bind(p);
        }

        return target[prop];

    }
});

But this seems very unclean/wrong.

3 Answers

You can bind the method on the constructor if this is the desired behavior:

constructor() {
      this.#hidden = false;
      this.toggle = this.toggle.bind(this);
}

Demo:

class Parent {

    #hidden;

    constructor() {
        this.#hidden = false;
        this.toggle = this.toggle.bind(this);
    }

    get hidden() {
        return this.#hidden;
    }

    toggle() {
        this.#hidden = !this.#hidden;
        console.log("Changed", this.#hidden)
        return this.#hidden;
    }

}


const p = new Parent();
const proxy = new Proxy(p, {
    get: (target, prop, receiver) => {
        return target[prop];
    }
});

console.log(p.toggle())
console.log(proxy.toggle())  // this is the problem
console.log(p.toggle())

Otherwise you can proxy the class itself:

class Parent {

    #hidden;

    constructor() {
        this.#hidden = false;
    }

    get hidden() {
        return this.#hidden;
    }

    toggle() {
        this.#hidden = !this.#hidden;
        //console.log("Changed", this.#hidden)
        return this.#hidden;
    }

}


const p = new Parent();
const ParentProxy = new Proxy(Parent, {
    get(target, prop, receiver) {
        return target[prop];
    }
});

const p2 = new ParentProxy();

console.log('p toggle:', p.toggle());
console.log('p2 toggle:', p2.toggle());  //
//console.log(proxy.toggle())  // this is the problem
console.log('p toggle:', p.toggle());
console.log('p2 toggle:', p2.toggle());

A Proxy object gives a direct access to the target and prop so you can simply access the privates as if you are directly accessing from the instance.

My approach would be like;

class Parent {

    #hidden;

    constructor() {
        this.#hidden = false;
    }

    get hidden() {
        return this.#hidden;
    }

    toggle() {
        this.#hidden = !this.#hidden;
        console.log("Changed", this.#hidden)
        return this.#hidden;
    }

}


var p = new Parent();
var proxy = new Proxy(p,{get: (target,prop,receiver) => _ => target[prop]()});
console.log(p.toggle())
console.log(proxy.toggle())  // this is the problem
console.log(p.toggle())

On a second level of thinkering, this in fact might become better such as;

class Parent {
    #hidden;

    constructor() {
        this.#hidden = false;
    }

    get hidden() {
        return this.#hidden;
    }

    toggle() {
        this.#hidden = !this.#hidden;
        console.log("Changed", this.#hidden)
        return this.#hidden;
    }

}


var p = new Parent();
var proxy = new Proxy(p,{get: (target,prop,receiver) => target[prop].bind(target)});
console.log(p.toggle())
console.log(proxy.toggle())  // this is the problem
console.log(p.toggle());

which clearly tells you that the Proxy object is not the instance itself. You should explicitly bind the passed function properties (or prototype methods) which have access to the "private" properties of the Class to target in order to make them function properly.

Related