最常見的 JavaScript 原型 (prototype) 面試題 :原型 (prototype)、原型鏈 (prototype chain) 、原型繼承 (prototypal inheritance)
2023年2月2日
學習 JavaScript 或面試時,原型通常是一個令許多人害怕的概念。理解「proto」、「prototype」、「_proto__」等中英文名詞已經讓人感到困惑,但這卻是面試中的高頻題目。本文將帶領讀者了解這些概念,並整理出面試的經典題目與回答方法。
問題一:原型 (prototype) 是什麼 ?
在 JavaScript 中,每個物件都包含了一個 [[Prototype]]
內部隱藏屬性,這個屬性對應到的就是該物件的原型 (prototype),值有可能是 null
或是指向另一個物件。但因為 [[Prototype]]
為內部屬性並無法直接被訪問到,所以瀏覽器提供了__proto__
的訪問方法,可參考下方程式碼。
但要注意, __proto__
方法並不在 ECMAScript 規範中,實際上開發要取得物件的原型會使用 Object.getPrototypeOf
。
// Person 是一個構造函式
function Person() {}
// 透過 Person 構造函式,創建了一個 personA 對象
const personA = new Person();
// 透過 __proto__ 方式,查看 personA 的原型
console.log(personA.__proto__); // {constructor: ƒ}
// personA 物件可以透過 __proto__ 方法訪問到它的原型
personA.__proto__ === Person.prototype; // true
Object.getPrototypeOf(personA) === Person.prototype; // true
personA.__proto__ === Object.getPrototypeOf(personA); // true
問題二:[[Prototype]]
又是什麼? 和 __proto__
的差別是什麼?
上面第一個問題已有提到,[[Prototype]]
是在 JavaScript 中物件的特殊隱藏屬性,但因為無法直接被訪問到,因此可以透過 __proto__
的訪問方法。
問題三:__proto__
屬性和 prototype
屬性的差別是什麼?
__proto__
和 prototype
是不同的屬性。 __proto__
是每個物件的一個隱藏屬性,每個物件可以由 __proto__
訪問到它的原型。而 prototype
是存在於所有構造函式中的一個屬性,構造函式的 prototype
其實和 __proto__
會指向同一個地方的,這個地方就叫做原型對象。(如下方程式碼)
// Person 是一個構造函式
function Person() {}
// 透過 Person 構造函式,創建了一個 personA 對象
const personA = new Person();
personA.__proto__ === Person.prototype; // true
問題四:原型鏈 (prototype chain) 是什麼 ?
原型 (prototype) 本身是一種物件,因此它也擁有自己的原型。當我們試圖訪問某個物件的屬性時,如果該物件沒有所需的屬性,它會在其原型 (prototype) 中尋找。如果原型 (prototype) 中仍然沒有找到,它將會繼續往上一層查找,直到找到,或者到達 null 為止。這條連續的路徑被稱為原型鏈 (prototype chain),鏈的終點值為 null。
personA.__proto__.__proto__.__proto__ === null;
例如,我們經常使用陣列的 filter
方法。假設現在有一個陣列 「list」,我們在這個陣列上使用 filter
方法。但事實上,filter
方法並不存在於這個 list,它存在於 Array
這個構造函式上。我們今天能使用 filter
方法,也是通過原型鏈 (prototype chain) 實現的。
問題五:什麼是原型繼承 (Prototypal inheritance)?
回答這個問題可以透過為什麼要有原型繼承 (Prototypal inheritance) 來理解。
假設今天有一個物件 「animal」,這個物件擁有自己的屬性和方法。同時我們又想建立兩個基於 「animal」 的物件,分別為分別為「cat」和「dog」,這兩個物件會有一些獨特的方法和屬性,但同時又需要用到「animal」物件的方法和屬性。在 JavaScript 裡,不需要透過複製或重新實現,就可以通過原型繼承 (Prototypal inheritance) 達成這個目的。
簡而言之,「cat」和「dog」物件本身雖然沒有「animal」物件的方法,但可以從它們的原型中繼承方法來使用。實際操作上,我們會將屬性或方法加到原型 (prototype) 上,那麼所有從這個物件中實例出來的物件,都有辦法使用這個方法或屬性。
// 構造函式 Animal
function Animal() {}
// 實例
const cat = new Animal();
// 往原型對象加上方法
Animal.prototype.sleep = function () {
console.log("sleep");
};
// 使用構造函式的 prototype 的方法
cat.sleep(); // sleep