最常見的 JavaScript 原型 (prototype) 面試題 :原型 (prototype)、原型鏈 (prototype chain) 、原型繼承 (prototypal inheritance)

2023年2月2日

💎 加入 E+ 成長計畫 與超過 300+ 位軟體工程師一同在社群中成長,並且獲得更多的軟體工程學習資源

學習 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
🧵 如果你想收到最即時的內容更新,可以在 FacebookInstagram 上追蹤我們