在学习原型与原型链之前,我认为你需要对 JavaScript 的内存模型(堆内存)足够的了解,否则你将无法理解原型以及原型链。
JavaScript 是一种基于原型的语言 (prototype-based language) ,在 JavaScript 中每一个对象中都有一个属性指向原型对象,或者说与其相关联。
原型 Prototype
原型的本质
在 JavaScript 中一个原型(prototype)就是一个对象,因此原型对象和原型实际上是一个意思。
我们都知道,对象是存储在堆(heap)内存当中的一块区域,原型对象也不例外。
接下来,我们通过 Array.prototype 这个原型对象(Prototype Object)来一步一步的揭示原型。

通过内存图看出,Array 构造函数的原型对象存在于一块堆内存空间中,且内存地址为 0x666 。
这个地址值是随便编的,此处只是举一个例子
我们已经知道了原型对象在内存中的位置。那么原型对象又在那些地方被引用了呢?
构造函数的 prototype
第一个引用到原型对象的地方就是构造函数(Constructor Function)。
我们知道,JavaScript 中函数也是一个对象,因此构造函数也会出现在堆内存当中。而构造函数作为一个对象,其中有一个名为 prototype 的属性,该属性存储着一个引用值,其指向的地址为 0x666 也就是 Array 构造函数的原型对象。
注意:只有函数才会有
prototype属性,而非构造函数中虽然存在prototype属性,但是没什么作用。
定理
我们直接粗暴的给出定理:什么构造函数就对应着什么原型对象。
比如,Array 这个构造函数所对应的原型对象便是 Array.prototype ,而 Object 这个构造函数所对应的原型对象便是 Object.prototype。
实例的 proto
另一个使用到了原型对象的地方便是实例对象中的 __proto__ 属性,内存图如下。
后文将会提到
__proto__与[[prototype]]之间的关系,现在可以将其看做同一个东西的不同写法。
定理
实例中的 __proto__ 的指向逻辑:谁构造的实例,实例中的 __proto__ 属性就指向谁的原型对象。
const arr = new Array(1, 2);const obj = new Object();
比如上述代码,arr 是 Array 构造函数的实例,因此 arr 的 __proto__ 指向的就是 Array.prototype ,而 obj 是 Object 构造函数的实例,因此 obj 的 __proto__ 指向的就是 Object.prototype
注意:Object.prototype.proto 存在特殊情况,其对应的值为 null,后文会用内存图的形式表达。
原型对象中的 constructor
我们将代码和内存图结合起来,并且增加点东西。
const arr = new Array(1, 2, 3);
上述代码的内存图如下:
这一次我们添加了两个细节:
- 实例对象 arr 中的全部属性
Array.prototype实例对象中的constructor属性
如图所示,在原型对象中,存在着一个名为 constructor 的属性,其存储着一个引用值,引用指向的地址为 0x222 也就是 Array 构造函数的内存地址。
定理
每一个原型对象(Prototype Object)上,都存在一个名为 constructor 的属性,该属性指向的值一定是一个构造函数,且该构造函数中的 prototype 属性一定指回该原型对象。
可能有点绕口,你也可以直接看下面这段代码并结合内存图来理解。
Object === Object.prototype.constructor // trueArray === Array.prototype.constructor // true

小结
我们复习一下,上面说到的几个定理:
- 什么构造函数就对应着什么原型对象。
- 谁构造的实例,实例中的
__proto__属性就指向谁的原型对象。 - 每一个原型对象(Prototype Object)上,都存在一个名为
constructor的属性,该属性指向的值一定是一个构造函数,且该构造函数中的prototype属性一定指回该原型对象。
我们可以配合下图理解:

原型链
原型链并不是什么高级的概念,我们将继续完善内存图,等我们完善好内存图的时候,原型链就自然而然的出来了。
构造函数的 proto
构造函数本质是什么?也是一个对象,因此也存在 __proto__ 属性。
因为是构造函数是一个函数,也就是说函数是 Function 构造函数的实例,也就是说构造函数是 Function 构造函数的实例。
所以,构造函数的 __proto__ 指向 Function.prototype 。

在上图中,我们又补充了 Function 构造函数在内存图中的情况。
我们依然根据上面的定律得出:
- Function 构造函数的
prototype指向的是Function.prototype。 - Function 构造函数本质是 Function 构造函数的实例,因此
__proto__指向Function.prototype
你可能会发现,Function 是一个鸡生蛋蛋生鸡的问题。其实并不是,其实并不是,Function 作为 JavaScript 内置的对象,并不是被构造出来的。
可以参考 hax 在知乎上的回答: https://www.zhihu.com/question/31333084/answer/152086175
构造函数原型对象的 proto
现在我们再把目光聚焦在 Array.prototype 上,也就是 Array 的原型对象,其本质也是一个对象,因此也会存在一个名为 __proto__ 的属性,其指向的地址是构造该实例的构造函数的原型对象的地址。因为该实例是一个普通的对象,因此其构造函数是 Object,而 Object 的原型对象是 Object.prototype 。
下面,我们画出原型图
新增的细节:
Object.prototype在内存中的示意图- Object 构造函数在内存中的示意图
Array.prototype中__proto__属性的引用Function.prototype中__proto__属性的引用Object.prototype中constructor属性的引用
一样,Object.prototype 也是一个对象,因此也会存在 __proto__ 属性,但是这里比较特殊,其对应的值是 null 。
那讲到这里,好像也没说什么是原型链。不过原型链其实已经出现了。我们在图中使用红色加粗的线来表示其中的一条原型链。
原型链通过对象的 __proto__ 属性相连接,最终在内存图中构成了一个链条,就是所谓的原型链。
arr.__proto__ -> Array.prototype.__proto__ -> Object.prototype.__proto__ -> null
关于 [[prototype]]
遵循 ECMAScript 标准,someObject.[[Prototype]] 符号是用于指向 someObject 的原型。从 ECMAScript 6 开始,[[Prototype]] 可以通过 Object.getPrototypeOf() 和 Object.setPrototypeOf() 访问器来访问。这个等同于 JavaScript 的非标准但许多浏览器实现的属性 proto。 —— MDN
[[prototype]] 是规范中的定义,并且提供了方法去设置和修改 [[prototype]]。而 __proto__ 是浏览器实现的属性,也可以做到同样的操作。
为了方便演示,我直接使用的 __proto__,实际使用中建议使用规范提供的方法,修改或访问原型。
总结
三个定理和一个特殊情况:
定理:
- 什么构造函数就对应着什么原型对象。
- 谁构造的实例,实例中的
__proto__属性就指向谁的原型对象。 - 每一个原型对象(Prototype Object)上,都存在一个名为
constructor的属性,该属性指向的值一定是一个构造函数,且该构造函数中的prototype属性一定指回该原型对象。
特殊情况:
Object.prototype指向null。
最后还需要注意理解为什么 Function 的 __proto__ 指向 Function.prototype,以及所谓鸡生蛋蛋生鸡的问题。
可以参考 hax 在知乎上的回答: https://www.zhihu.com/question/31333084/answer/152086175
最后,下面这种图非常好的描述了原型链在内存中的情况。

