考虑这段代码:
class Klass {
#priv = 42
get pub() {
return this.#priv
}
}
// these two correctly return 42
console.log(new Klass().pub);
console.log((new class extends Klass {}).pub)
// but this one throws "TypeError: Cannot read private member
// #priv from an object whose class did not declare it"
console.log(Object.create(new Klass()).pub) // throws
// even though
console.log(Object.create(new Klass()) instanceof Klass) // true
console.log(Object.getPrototypeOf(Object.create(new Klass())).pub) // 42
我认为既然我在原型链中有一个
Klass
的真实实例,那么访问 Object.create(new Klass()).pub
就不会抛出异常。
这是设计使然吗?如果是的话,为什么要这样做呢? 另外,使用通用函数来克隆行为类似于
Klass
的任意类实例的正确方法是什么?
我在用 vitest 测试某些东西时遇到了这个问题。我的代码看起来像这样:
import { it, expect } from 'vitest';
class Klass {
#priv = 42;
get pub() {
return this.#priv;
}
}
it('works', () => {
expect(new Klass()).toMatchObject({
make_this_test_fail: 'yup',
pub: 42,
});
});
我的测试显然失败了,但没有给我很好的差异显示该属性
make_this_test_fail
在它显示的实例上没有找到
FAIL test/the.test.js [ test/the.test.js ]
TypeError: Cannot read private member #priv from an object whose class did not declare it
❯ Klass.get pub [as pub] test/the.test.js:7:15
5|
6| get pub() {
7| return this.#priv;
| ^
8| }
9| }
我将其跟踪到 this 行,其中
actual
来自 deepClone
,它使用 Object.create
here 创建任意类的实例。
这是设计使然吗?如果是的话为什么要这样做?
是的,这是设计使然:私有字段不会沿原型链继承,它们仅存在于构造期间创建它们的特定实例上。这使得它们从外部完全无法检测到,您不能通过例如以下方式弄乱私有字段:交换对象的原型,并且您不能欺骗方法在以前不存在的对象上创建私有字段(通过分配,在示例中考虑 setter)。
私有字段是“硬私有”,非常类似于内置对象的内部槽。它们也不会被代理转发。私有字段的心智模型就像一个
WeakMap
存储每个实例的值。
(也就是说,不是每个人都同意这是一个好的设计。)
此外,使用通用函数来克隆行为类似于
的任意类实例的正确方法是什么?Klass
你无法编写这样的通用函数。任何类,无论是否使用私有字段,都可以具有不可克隆的私有状态。实现这项工作的唯一方法是为每个类提供一个
clone
方法,以适当的方式克隆自身。如果您想避免冲突,请使用方法名称的符号。