最常见的 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