最常见的 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 上追蹤我們