前言
什么是原型链
每个对象(Object)都有一个私有属性指向另一个名为原型(prototype)的对象。原型对象也有一个自己的原型,层层向上直到一个对象的原型为
null
。根据定义,null
没有原型,并作为这个原型链(prototype chain)中的最后一个环节。摘自:MDN - 继承与原型链
说明
__proto__
实际为[[Prototype]]
属性的访问器,为了便于理解,本文以属性代称其访问器实质这里不使用
class
表达式是因为class
表达式实际上是特殊的函数,更类似于将多个操作融合后的语法糖。使用Function
来理解更为直观所有代码均已在
Chrome
浏览器v125.0.6422.142
经过结果验证
一、名词解释
在开始了解原型链之前,先介绍两个名词prototype
以及__proto__
,举一个简单的例子更为直观
定义一个函数Foo
,而后创建一个Foo
的实例对象o1
。
function Foo(){} const o1 = new Foo()
(一)、原型对象(prototype
)
[!NOTE]
所有的函数都是对象,拥有独有属性
prototype
prototype
原型对象,是函数的独有属性。该属性指向一个对象。当函数(如:Foo
)被实例化成一个对象(如:o1
)后,实例对象(o1
)可以访问到函数(Foo
)的原型对象(prototype
)
如:在Foo
的prototype
上增加属性propA
,其值为'p1'
,可以发现在o1
上也可以获取到属性propA
,其值同样为'p1'
Foo.prototype.propA = 'p1' o1.propA // 'p1'
那么o1
是如何获取到Foo.prototype
上的方法的呢,这就要介绍另一个概念,隐式原型__proto__
(二)、隐式原型(__proto__
)
[!Note]
所有非内置对象都是函数的实例,拥有独有属性
__proto__
__proto__
隐式原型,是对象的独有属性。对象(如:o1
)的__proto__
属性指向其构造函数(如:Foo
)的原型对象( prototype
)。 所有非内置对象都是函数的实例,同时拥有一个构造函数。如:对象o1
的构造函数为Foo
内置对象如:
Function
、Date
、Array
、Object
、Math
、JSON
等。
如:o1
上访问到的属性propA
实际上是其构造函数Foo
的prototype
的属性propA
。这里将Foo.prototype.propA
设置为一个对象,来防止因基本类型的值比较方式导致结论误差
o1.constructor === Foo // true Foo.prototype.propA = {} o1.propA // {} o1.propA === o1.__proto__.propA // true o1.__proto__.propA === Foo.prototype.propA // true
二、原型链
在介绍何为prototype
以及__proto__
后,接下来就开始介绍原型链了。仍以之前使用的例子来进行原型链的介绍
function Foo(){} const o1 = new Foo()
(一)、实例化关系
首先一起来分析例子的实例化关系。可在Chrome
中测试如下代码
o1.constructor === Foo // true Foo.constructor === Function // true Function.constructor === Function // true
根据验证结果可分析出如下图结论
o1
的数据类型是对象,是函数Foo
的实例化Foo
的数据类型既是函数也是对象,其是Function
的实例化Function
既是函数也是对象,其是自身Function
的实例化
(二)、独有属性分析
在实例化关系的基础上,继续分析每一级的属性关系。其中绿色代表函数独有属性,红色代表对象独有属性
o1
的数据类型为对象,拥有独有属性__proto__
,由于prototype
是函数独有属性,所以o1
上的prototype
为undefined
Foo
的数据类型既是函数也是对象,所以其同时拥有属性prototype
和__proto__
Function
的数据类型既是函数也是对象,所以其同时拥有属性prototype
和__proto__
(三)、隐式原型引用关系
[!NOTE]
对象的隐式原型(
__proto__
)属性指向其构造函数(constructor
)的原型对象(prototype
)
o1.__proto__
指向其构造函数Foo
的原型对象(prototype
)Foo.__proto__
的指向其构造函数Function
的原型对象(prototype
)由于
Function
的构造函数是其自身,所以Function.__proto__
指向其自身的原型对象(prototype
)
由于函数的原型对象( prototype
)属性的数据类型为对象,因此同样具有对象的独有属性__proto__
。如下图
默认情况下,对象隐式原型( __proto__
)指向其构造函数的原型对象( prototype
),那么Foo.prototype
和Function.prototype
的构造函数有指向哪里呢?
[!NOTE]
所有函数的原型对象(
prototype
)的构造函数均指向其自身
可通过一下测试代码进行验证
Foo.prototype.constructor === Foo // true Function.prototype.constructor === Function // true
内置函数对象的原型对象( prototype
),其隐式原型( __proto__
)也指向其自身
RegExp.prototype.constructor === RegExp // true Date.prototype.constructor === Date // true Map.prototype.constructor === Map // true Array.prototype.constructor === Array // true Number.prototype.constructor === Number // true Object.prototype.constructor === Object // true
然而一些内置对象由于其没有函数特征,所以其原型对象( prototype
)属性为undefined
,其自身的constructor
指向Object
。
Math.prototype // undefined Math.constructor === Object // true JSON.prototype // undefined JSON.constructor === Object // true
言归正传,由于所有函数的原型对象( prototype
)的构造函数均为其自身,则如若Foo.prototype.__proto__
指向其构造函数的prototype
,即Foo.prototype.__proto__
指向Foo.prototype
。那么原型链的查找将进入无限循环。为了避免这个问题,则将所有函数原型对象( prototype
)的隐式原型(__proto__
)均指向Object.prototype
。
可通过以下代码进行验证:
Foo.prototype.__proto__ === Object.prototype // true Function.prototype.__proto__ === Object.prototype // true
这里欠缺的有两个点
Object
既是函数也是对象,所以其拥有对象的独有属性(__proto__
),那么其隐式原型(__proto__
)指向哪里Object.prototype
的数据类型为一个对象,如果其隐式原型(__proto__
)仍指向Object.prototype
,那么原型链的查找将进入无限循环,那么其指向哪里
针对第1点,Object
自身既是函数又是对象,其作为对象的独有属性隐式原型( __proto__
)应指向其构造函数的原型对象( prototype
)。Object
对象的构造函数为Function
,所以其隐式原型( __proto__
)指向Function.prototype
。
可通过以下测试代码进行验证
Object.constructor === Function // true Object.__proto__ === Function.prototype // true
因此其指向关系如下图:
针对第2点,文章起始什么是原型链已经给出了定义。
原型对象也有一个自己的原型,层层向上直到一个对象的原型为 null
因此其最终指向为null
三、总结:
对象通过隐式原型( __proto__
)属性指向其构造函数的原型对象( prototype
),进而通过原型对象( prototype
)的隐式原型( __proto__
)属性指向更高层级的原型对象( prototype
),最终指向null
而停止所形成的链条,则称其为原型链。