TypeError: can't access/set private field or method: object is not the right class

当私有字段或方法在未定义该私有元素的对象上进行获取或设置时,会发生 JavaScript 异常“无法访问私有字段或方法:对象不是正确的类”或“无法设置私有字段:对象不是正确的类”。

消息

TypeError: Cannot read private member #x from an object whose class did not declare it (V8-based)
TypeError: Cannot write private member #x to an object whose class did not declare it (V8-based)
TypeError: can't access private field or method: object is not the right class (Firefox)
TypeError: can't set private field: object is not the right class (Firefox)
TypeError: Cannot access invalid private field (evaluating 'this.#x') (Safari)

错误类型

TypeError

哪里出错了?

你正在尝试在一个对象上获取或设置私有字段或方法,但该对象不包含此私有元素。私有实例属性只能在其声明的类的实例(包括其子类)上访问;私有静态属性只能在其声明的类本身上访问,而不能在子类上访问。

当私有名称存在于类作用域中,但访问它的对象无效时,会发生此错误。如果私有名称不存在,你将得到一个语法错误

示例

静态/实例字段不匹配

你可能将字段声明为静态字段,但却尝试在实例上访问它,反之亦然。

js
class MyClass {
  static #x = 0;
  doSomething() {
    console.log(this.#x);
  }
}

const obj = new MyClass();
obj.doSomething();
// TypeError: can't access private field: object is not the right class

要解决此问题,可以更改字段为实例字段,或者在类本身上访问该字段,或者在实例上声明另一个字段。请注意,私有命名空间在静态属性和实例属性之间共享,因此不能拥有同名的静态和实例私有元素。

js
class MyClass {
  #x = 0;
  doSomething() {
    console.log(this.#x);
  }
}

class MyClass2 {
  static #x = 0;
  doSomething() {
    console.log(MyClass2.#x);
  }
}

使用了错误的对象

你可能有一个访问 this.#x 的方法,但它被以另一个 this 值调用了。

js
class JSONReplacer {
  #count = 0;
  func(key, value) {
    if (typeof value === "object") {
      this.#count++;
    }
    return value;
  }
}

JSON.stringify({ a: 1, b: { c: 2 } }, new JSONReplacer().func);
// TypeError: can't access private field: object is not the right class

这是因为 JSON.stringify() 会将包含 value 的对象作为 this 来调用替换函数,因此私有字段不可访问。要解决此问题,你可以将方法绑定到对象,或使用箭头函数,以确保 replacer.func 以正确的 this 值被调用。

js
const replacer = new JSONReplacer();
JSON.stringify({ a: 1, b: { c: 2 } }, replacer.func.bind(replacer));
JSON.stringify({ a: 1, b: { c: 2 } }, (...args) => replacer.func(...args));

大多数情况下,如果你不小心解除了方法的绑定,该方法将以 undefined 作为 this 被调用,这将导致另一个错误(TypeError: 无法将 undefined 转换为对象)。此错误仅在方法以不同的对象作为 this 被调用时发生,无论是通过使用 call()apply(),还是通过将方法作为回调函数传递给以不同 this 值调用它的函数。

如果你不确定对象是否包含私有元素,如下面的代码所示:

js
class MyClass {
  #x = 0;
  static doSomething(obj) {
    console.log(obj.#x); // Throws if obj is not an instance of MyClass
  }
}

你可以首先使用 in 运算符执行品牌检查

js
class MyClass {
  #x = 0;
  static doSomething(obj) {
    if (!(#x in obj)) {
      return;
    }
    console.log(obj.#x);
  }
}

在子类上访问静态元素

如果你有一个私有静态属性,你只能在声明它的类上访问它,而不能在子类上访问。

js
class MyClass {
  static #x = 0;
  doSomething() {
    console.log(this.#x);
  }
}

class MySubClass extends MyClass {}

MySubClass.doSomething();
// TypeError: can't access private field: object is not the right class

为了解决这个问题,永远不要通过 this 访问私有静态属性。相反,请始终明确指定类的名称。

js
class MyClass {
  static #x = 0;
  doSomething() {
    console.log(MyClass.#x);
  }
}

访问另一个类中同名的私有元素

与普通字符串或 Symbol 属性不同,私有名称在类之间不共享。如果两个类中都有同名的私有元素,它们仍然不是相同的元素,你不能从一个类访问另一个类的私有元素。

js
class MyClass {
  #x = 0;
}

class MyOtherClass {
  #x = 1;
  doSomething(o) {
    console.log(o.#x);
  }
}

const obj = new MyClass();
new MyOtherClass().doSomething(obj);
// TypeError: can't access private field: object is not the right class

向不相关对象添加私有元素

你不能动态地添加私有元素到不相关的对象。

js
class MyClass {
  #x = 0;
  static stamp(obj) {
    obj.#x = 1;
  }
}

MyClass.stamp({});
// TypeError: can't set private field: object is not the right class

如果你真的想这样做,可以考虑返回覆盖技巧。然而,通常情况下,你可能更倾向于使用 WeakMap

js
class MyClass {
  static #objToX = new WeakMap();
  static stamp(obj) {
    MyClass.#objToX.set(obj, 1);
  }
}

MyClass.stamp({});

另见