1. 原型

prototype

每个函数都有一个 prototype 属性(PS:prototype 是函数才会有的属性噢),比如:

  1. function Car() {}
  2. Car.prototype.color = 'black';
  3. var car1 = new Car();
  4. var car2 = new Car();
  5. console.log(car1.color) // black
  6. console.log(car2.color) // black

那【Car】这个函数的 prototype 属性到底指向的是什么,又为什么需要这样的设计?我们可以把它打印出来看看:
image.png
可以看出,prototype 属性指向了一个对象,这个对象就是我们常说的原型。这个对象在 JS 设计之初,是专门设计用来存储对象共享的属性。例如上面那个例子,car1 和 car2 两个对象,它们通过 prototype 属性拥有了完全相同的 color (如果修改了 prototype 的color,所有 Car 对象的 color 都会被修改)。

因此,我们可以得出一个构造函数和实例原型的关系图:
01-原型和原型链 - 图2
那么,我们该怎么表示实例原型与实例对象,也就是 car1 和 Car.prototype 之间的关系呢?这时候我们就要讲到第二个属性:proto

proto

在 JS 所有对象中(除了null)都具有的一个属性,叫 proto,这个属性会指向该对象的原型。

  1. console.log(car1.__proto__ === Car.prototype); // true

于是更新构造函数和实例原型的关系图如下:
01-原型和原型链 - 图3
既然实例对象和构造函数都可以指向原型,那么实例原型是否有属性指向构造函数或者实例对象呢?

实例原型没有属性指向实例对象,因为一个构造函数可以生成多个实例。
实例原型有属性 constructor 指向构造函数。

constructor

每个实例原型都有一个 constructor 属性指向关联的构造函数:

  1. console.log(Car === Car.prototype.constructor); // true

于是更新构造函数和实例原型的关系图如下:
01-原型和原型链 - 图4
综上所述,构造函数、实例原型、和实例对象之间的关系如上图,用代码表示为:

  1. function Car() {}
  2. Car.prototype.color = 'black';
  3. var car1 = new Car();
  4. var car2 = new Car();
  5. console.log(car1.color) // black
  6. console.log(car2.color) // black
  7. console.log(car1.__proto__ == Car.prototype) // true
  8. console.log(Car.prototype.constructor == Car) // true

2. 原型链

原型链是什么?其实理解了原型,原型链不是一个十分复杂的概念。

上面讲到,每个对象都有一个 proto 属性指向实例原型,那么这个实例原型也有 proto 指向它的实例原型,直到指向 null,这些相互关联的原型组成的链状结构就是原型链。

我们可以根据上面的 Car 分析一下它的原型链,先打印一下 Car.prototype:
image.png
可以看到,Car 的实例原型的 proto 指向的是 Object 的 prototype,而 Object.prototype 的 proto 又指向了null(null 没有构造函数),这就是它的原型链。因此,更新关系图如下:
01-原型和原型链 - 图6

参考资料:

  1. 前端基础进阶(十一):详解面向对象、构造函数、原型与原型链
  2. 【重点】图解:告诉面试官什么是 JS 原型和原型链
  3. JavaScript深入之从原型到原型链