JavaScript 一面:原型链、继承与 instanceof
原型和继承几乎是每一份前端面试题里都会出现的老面孔,这一篇尽量用“画图 + 几个典型问题”的方式,把原型链相关的考点串起来。
原型对象与原型链:先把那几根箭头理清楚
在 JS 里,几乎每个对象背后都有一根“指向它父辈”的指针:
- 对象的内部属性
[[Prototype]](大多数环境下可以通过__proto__访问)指向它的原型对象; - 构造函数的
prototype属性指向“实例的原型对象”; - 原型对象本身也是对象,也有自己的
[[Prototype]],一直往上,最后指向Object.prototype,再往上就是null。
可以用一个简化的关系图描述:
1function Foo() {}
2
3const foo = new Foo();
4
5foo.__proto__ === Foo.prototype
6Foo.prototype.__proto__ === Object.prototype
7Object.prototype.__proto__ === null
这条一层层向上的链,就是所谓的“原型链”。
当访问 foo.someProp 时,引擎的查找路径是:
- 看
foo自身有没有someProp; - 没有就去
foo.__proto__(也就是Foo.prototype)上找; - 还没有就继续沿着
__proto__往上找,直到Object.prototype或null。
理解了这条查找路径,后面很多原型链相关的题就自然了。
instanceof 的原理:看右边 prototype 在不在左边的原型链上
instanceof 是一面常客,很多题都围绕它做文章。
它的语义可以简化成一句话:
foo instanceof Foo判断的是:Foo.prototype是否出现在foo的原型链上。
用伪代码来描述大致逻辑:
1function myInstanceOf(obj, Ctor) {
2 if (obj == null || (typeof obj !== "object" && typeof obj !== "function")) {
3 return false;
4 }
5
6 let proto = Object.getPrototypeOf(obj);
7 const target = Ctor.prototype;
8
9 while (proto) {
10 if (proto === target) return true;
11 proto = Object.getPrototypeOf(proto);
12 }
13
14 return false;
15}
面试时如果被问到“instanceof 的原理能不能简单说一下”,
只要说明“沿着左边对象的原型链往上找,看是否能遇到右边构造函数的 prototype”,就足够了。
手写继承:从 ES5 到 class 的几种常见写法
在一面里,有时会要求你“手写一个简单的继承方案”,常见考点包括:
- 基于原型的继承:
- 子类型的
prototype指向父类型的实例或原型; - 保证
constructor指回子类型本身。
- 子类型的
例如一个常见的组合继承写法:
1function Animal(name) {
2 this.name = name;
3}
4Animal.prototype.sayHi = function () {
5 console.log("Hi, I'm " + this.name);
6};
7
8function Dog(name, color) {
9 Animal.call(this, name); // 借用构造函数,初始化实例属性
10 this.color = color;
11}
12
13Dog.prototype = Object.create(Animal.prototype); // 原型链继承
14Dog.prototype.constructor = Dog; // 修正 constructor
15
16Dog.prototype.bark = function () {
17 console.log("Woof!");
18};
class 语法本质上是对这种模式的语法糖,理解底层这一套之后,看 class 的行为会更自然。
原型链相关的常见一面题
一些典型的考法(题目不一定一模一样,但思路类似):
- 判断输出与等式真假:
1function Foo() {}
2const foo = new Foo();
3
4console.log(foo.__proto__ === Foo.prototype); // ?
5console.log(Foo.prototype.__proto__ === Object.prototype); // ?
6console.log(foo.__proto__.__proto__ === Object.prototype); // ?
7console.log(foo instanceof Foo); // ?
8console.log(foo instanceof Object); // ?
- 判断
instanceof在边界情况下的表现:
1console.log([] instanceof Array); // true
2console.log([] instanceof Object); // true
3console.log(Object.create(null) instanceof Object); // ?
- 混合 class 与函数构造器的继承题,考察是否理解
extends背后的原型链形态。
面对这类题,建议不要硬背答案,而是养成“从对象往上沿原型链走一遍”的习惯。
常见面试题与参考答案
题 1:几条原型链等式的结果?
1function Foo() {}
2const foo = new Foo();
3
4console.log(foo.__proto__ === Foo.prototype); // ?
5console.log(Foo.prototype.__proto__ === Object.prototype); // ?
6console.log(foo.__proto__.__proto__ === Object.prototype); // ?
7console.log(foo instanceof Foo); // ?
8console.log(foo instanceof Object); // ?
参考答案:
foo.__proto__ === Foo.prototype→true- 实例的内部原型指向构造函数的
prototype。
- 实例的内部原型指向构造函数的
Foo.prototype.__proto__ === Object.prototype→true- 自定义构造函数的原型对象默认继承自
Object.prototype。
- 自定义构造函数的原型对象默认继承自
foo.__proto__.__proto__ === Object.prototype→true- 沿着原型链再往上一层就是
Object.prototype。
- 沿着原型链再往上一层就是
foo instanceof Foo→trueFoo.prototype在foo的原型链上。
foo instanceof Object→trueObject.prototype也在foo的原型链上。
这里可以顺势再强调一句:instanceof 本质就是“右侧 prototype 是否出现在左侧对象的原型链上”。
题 2:几种特殊对象的 instanceof 结果?
1console.log([] instanceof Array); // ?
2console.log([] instanceof Object); // ?
3console.log(Object.create(null) instanceof Object); // ?
参考答案:
[] instanceof Array→true- 数组实例的原型链上包含
Array.prototype。
- 数组实例的原型链上包含
[] instanceof Object→trueArray.prototype.__proto__ === Object.prototype,因此Object.prototype也在原型链上。
Object.create(null) instanceof Object→false- 这个对象的原型是
null,原型链上没有Object.prototype。
- 这个对象的原型是
这道题的目的,是考你对“原型链起点可以不是 Object.prototype”这一点有没有概念。
题 3:简单实现一个继承关系
题目可能会说:用 ES5 写一个
Animal/Dog的继承关系,让Dog继承Animal的sayHi方法。
可以给出类似这样的代码并简要说明:
1function Animal(name) {
2 this.name = name;
3}
4Animal.prototype.sayHi = function () {
5 console.log("Hi, I'm " + this.name);
6};
7
8function Dog(name, color) {
9 Animal.call(this, name); // 借用构造函数,复用初始化逻辑
10 this.color = color;
11}
12
13Dog.prototype = Object.create(Animal.prototype); // 建立原型链
14Dog.prototype.constructor = Dog; // 修正 constructor 指针
15
16Dog.prototype.bark = function () {
17 console.log("Woof!");
18};
关键解释点:
- 用
Animal.call(this, name)继承实例属性; - 用
Object.create(Animal.prototype)让Dog实例在原型链上能访问到Animal.prototype上的方法; - 最后记得把
constructor指回Dog本身。
和工程实践的连接:为什么还要关心原型?
在日常项目里,很多人很少直接操作 __proto__ 或 prototype,
但原型相关的概念在这些地方仍然很重要:
- 理解内建对象的行为:
Array.prototype、Map.prototype等; - 看懂一些框架/库里对原型的魔改(如给原型加方法);
- 排查某些“方法怎么突然没了/被覆盖了”的诡异 bug。
在一面回答原型链问题时,如果能顺带提一下这些工程上的例子,比只讲裸概念更有说服力。
小结:一面里如何自然地讲“原型链”?
可以用一个简单的结构来组织回答:
- 先用那条“
foo.__proto__ → Foo.prototype → Object.prototype → null”的链,说明什么是原型链; - 接着用一句话讲清
instanceof的原理,并简单说一下手写实现思路; - 再补一个简短的继承例子(函数构造器 +
Object.create); - 最后用一两句带到工程实践里的用处。
这样既覆盖了高频考点,也能展示你不只是“会做题”,而是确实理解了这一块的运行机制。