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
哪裡出錯了?
你正在嘗試在一個物件上獲取或設定私有欄位或方法,但該物件不包含此私有元素。私有例項屬性只能在其宣告的類的例項(包括其子類)上訪問;私有靜態屬性只能在其宣告的類本身上訪問,而不能在子類上訪問。
當私有名稱存在於類作用域中,但訪問它的物件無效時,會發生此錯誤。如果私有名稱不存在,你將得到一個語法錯誤。
示例
靜態/例項欄位不匹配
你可能將欄位宣告為靜態欄位,但卻嘗試在例項上訪問它,反之亦然。
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
要解決此問題,可以更改欄位為例項欄位,或者在類本身上訪問該欄位,或者在例項上宣告另一個欄位。請注意,私有名稱空間在靜態屬性和例項屬性之間共享,因此不能擁有同名的靜態和例項私有元素。
class MyClass {
#x = 0;
doSomething() {
console.log(this.#x);
}
}
class MyClass2 {
static #x = 0;
doSomething() {
console.log(MyClass2.#x);
}
}
使用了錯誤的物件
你可能有一個訪問 this.#x 的方法,但它被以另一個 this 值呼叫了。
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 值被呼叫。
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 值呼叫它的函式。
如果你不確定物件是否包含私有元素,如下面的程式碼所示:
class MyClass {
#x = 0;
static doSomething(obj) {
console.log(obj.#x); // Throws if obj is not an instance of MyClass
}
}
你可以首先使用 in 運算子執行品牌檢查。
class MyClass {
#x = 0;
static doSomething(obj) {
if (!(#x in obj)) {
return;
}
console.log(obj.#x);
}
}
在子類上訪問靜態元素
如果你有一個私有靜態屬性,你只能在宣告它的類上訪問它,而不能在子類上訪問。
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 訪問私有靜態屬性。相反,請始終明確指定類的名稱。
class MyClass {
static #x = 0;
doSomething() {
console.log(MyClass.#x);
}
}
訪問另一個類中同名的私有元素
與普通字串或 Symbol 屬性不同,私有名稱在類之間不共享。如果兩個類中都有同名的私有元素,它們仍然不是相同的元素,你不能從一個類訪問另一個類的私有元素。
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
向不相關物件新增私有元素
你不能動態地新增私有元素到不相關的物件。
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。
class MyClass {
static #objToX = new WeakMap();
static stamp(obj) {
MyClass.#objToX.set(obj, 1);
}
}
MyClass.stamp({});