ES6代理类,访问私有属性(无法从未声明其类的对象中读取私有成员#hidden)

9
我正在尝试使用代理对象、类和私有属性进行实验。 然后遇到了这个错误信息:
/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

重现代码:

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())

有没有办法处理代理类实例上的私有属性?

为什么代理无法处理这个?

感谢任何提示/答案。

编辑:在 github 上找到了一个相关问题:https://github.com/tc39/proposal-class-fields/issues/106 我发现的一个快速 hack 是使用:

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];

    }
});

但这似乎非常不干净/错误。


1
出于好奇,使用代理的动机是什么?也许有其他选择。 - loganfsmyth
这就是为什么私有字段不是一个好主意。 - Bergi
@loganfsmyth 很抱歉回答晚了:我想要将两个对象“合并”在一起。两个不同方法/属性的独立对象应该通过一个作为“后备”的对象进行访问。如果 obj1 上不存在属性 x,则尝试 obj2。为此,代理应该“透明”地工作。我认为我会使用 ... instanceof Function/.bind(...) 进行检查。 - Marc
3个回答

5

如果需要,您可以将方法绑定到构造函数上:

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

示例:

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())

否则,您可以代理类本身:

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());


1
注意:您必须使用handler.constructnewTarget参数 - OP根本没有使用construct陷阱?这很好用。在您的示例中,您的处理程序除了将完全相同的参数分派到Reflect.construct之外什么也没做,这与不捕获construct是一样的。 - Bergi
哦,我的错。你是对的,谢谢指出。 - Fraction

1

问题在于您试图从代理访问私有实例,而不是从类的内部访问。get: () => { // here we are accessing from the proxy not from the class itself} 所以这是预期行为。 - Ayzrian
有道理,但是为什么我可以记录属性 console.log(p.hidden, proxy.hidden)?这个有效... 我本来以为这也会失败。 - Marc
是的,这也是预期的。toggle不是私有的,但变量在toggle调用内部被访问,因此类正在访问其私有字段。 - Ayzrian
如果这解决了您的问题,请将答案标记为解决方案。您可以在此处阅读有关私有字段的更多信息:https://developer.cdn.mozilla.net/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields - Ayzrian
不,这并没有回答我的问题。问题是如何使用私有属性和代理。在您提供的链接中,没有一个字关于代理或者这种方法不起作用的说明。但还是谢谢。 - Marc
显示剩余3条评论

1
一个代理对象提供了直接访问目标属性的方式,因此您可以像直接从实例中访问私有内容一样简单地访问它们。
我的方法是这样的;

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())

在更深层次的思考中,这实际上可能会变得更好,例如:

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());

这段文字明确告诉你,Proxy对象并不是实例本身。你需要显式地将传递的函数属性(或原型方法)与Class的“私有”属性绑定到target上,以使它们正常运行。

@Bergi。是的,但这不是什么大问题。当我们对“target [prop]”应用函数测试时,它可以简单地工作。 - Redu
那么请这么做。 - Bergi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接