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

另見