只看一次絕對學不會的 JavaScript 原型指南:初探原型、原型繼承與原型鏈
相信許多初次接觸 JavaScript 原型的人都跟我有過同樣的經歷,在開始深入學習之前就被一連串相似的術語名詞弄得暈頭轉向,如「proto」
、「prototype」
、「[[Prototype]]」
、「原型 (prototype)」
、「原型鏈 (prototype chain)」
、「原型繼承 (prototypal inheritance)」
等。這些術語表面上相似,但實際上指涉的概念卻不相同,很容易混淆,即便我已經接觸 JavaScript 好一段時間了,要理解這些原型相關的知識還是花了我不少時間。
雖然在日常開發中,我們幾乎不會直接操作到物件的原型,但我認為,若想對 JavaScript 這個語言有更深層的理解,花一點時間學習原型仍然是非常重要且值得的。原型繼承雖然是一個抽象的概念,但卻是這門語言物件導向程式設計的核心基石。透徹理解原型,將為我們開啟對 JavaScript 更深層且宏觀的視野。由於原型涵蓋的概念與知識量很多,為了方便閱讀,我將會我對原型的理解拆成幾篇文章來介紹。本篇文章將會先簡單介紹原型(Prototype) 、原型鏈(Prototype chain) 與原型繼承(Prototypal inheritance) 的概念。本篇文章將帶領讀者先一步步認識 JS 原型中常見的名詞,以及最基本的概念,希望能夠幫助讀者建立起對 JS 原型的基礎認識。
原型 (Prototype)
JavaScript 中的原型(prototype)是什麼?
在 JavaScript 中,每個物件都有一個隱藏屬性 [[Prototype]]
,這個屬性指向另一個物件,這個被指向的物件稱為該物件的「原型」。原型的作用是讓物件可以繼承其他物件的屬性和方法,這是一種實現繼承的方式。
由於 [[Prototype]]
為內部屬性並無法直接被訪問到,JavaScript 提供了一個方便的屬性: __proto__
,來存取和修改物件的原型。__proto__
是一個非標準但被大多數現代瀏覽器支持的屬性,用於讀取和設置物件的原型。
例如:
let animal = {
eats: true
};
let rabbit = {
jumps: true
};
rabbit.__proto__ = animal;
console.log(rabbit.eats); // true,從 animal 繼承
console.log(rabbit.jumps); // true,rabbit 自己的屬性
在這個例子中,我們使用 __proto__
來設置 rabbit 的原型為 animal,從而使 rabbit 繼承了 animal 的屬性。
雖然 __proto__
在大多數瀏覽器中都被支持,但它並不是標準的一部分。為了更好的跨平台兼容性和程式碼的可讀性,建議使用標準的 Object.getPrototypeOf
和 Object.setPrototypeOf
方法來訪問和設置原型。
原型(prototype) 的特性
-
所有物件都有原型
在 JavaScript 中,幾乎所有的物件在創建時都會自動被賦予一個原型,只有
Object.prototype
的原型是null
(這意味著Object.prototype
是所有物件原型鏈的終點)。let obj = {};
console.log(Object.getPrototypeOf(obj) === Object.prototype); // true -
原型污染
由於所有繼承同一原型的物件都共享該原型的屬性和方法,因此修改原型可能會導致所有繼承該原型的物件發生變化。這在某些情況下可能會導致意料之外的行為,這種現象稱為「原型污染」。
let animal = {
eats: true
};
let rabbit = {
jumps: true,
__proto__: animal
};
animal.eats = false;
console.log(rabbit.eats); // false