一文搞懂JS原型链

143 阅读3分钟

引言

JavaScript原型链这个知识点让不少JavaScript初学者望而却步,或者干脆直接记下来。本文面向有基本JavaScript或编程语言基础的读者,不追求过于详细地“追根溯源”,聚焦于原型链本身,带你一起“手绘”一遍原型链。笔者也是初学者,有误烦请大佬指正。 接下来我们一起开始吧!

从普通对象实例出发

在JavaScript中,也许有很多创建对象实例的形式,字面量{}、Object.create()等。但从创建实例需要进行分配内存和设置原型的角度来看,实例的创建方式都涉及类似new的机制,我们最常用的也是通过new创建一个实例。因此,我们先从最熟悉的对象实例出发: 对象实例a由new A()创建,并设置其__proto__A.prototype

我们可以在控制台验证一下:

对象构造函数的prototype

我们在上图可以发现,A方法有一个prototype属性、new出来的实例隐式属性__proto__也指向prototype,那么这玩意儿是哪来的呢?我们控制台输出一下:

那这玩意儿看起来也应该是一个对象,既然prototype是一个对象,那么它是不是也有__proto__,我们输出看一下:

从图中我们可以看到A.prototype.__proto__的构造函数是Object()看到了我们熟悉的东西,接下来我们聚焦这个prototype

我们同样在控制台验证一下:

画到这,我们发现,怎么又多了一个prototype,没错!Object()也是个对象,那是对象肯定也有自己的原型,所以图中右上角新出现的prototype就是Object()的原型。 和我们上文研究第一个prototype类似,第二个prototype是不是也有__proto__呢?我们控制台看一下:

没了!变成null了。没错!到头了!即“原型链终止于拥有null作为其原型的对象上。”

原型链终止于拥有 null 作为其原型的对象上。——《MDN》

完善一下上图,就是:

所以所谓“原型链”,就是图中红色标注的链条。

构造函数是如何创建的

那么这张图画到这,是不是就结束了呢?总感觉还有什么东西没弄明白?在上图中a对象和中间处的prototype都是通过new关键字创建的,可是构造函数A()和Object()不也是对象吗?他们怎么来的呢? 我们先弄明白一个构造函数咋来的,另一个也是类似的,那么我们接下来聚焦于Object构造函数。我们知道函数在JavaScript中是有Function构造函数创建的,于是就可以画出下图:

控制台输出看看:

从图中,我们又有一个疑问了,那Function__proto__被设置为啥呢?为了避免出现“先有鸡还是先有蛋”的古老谜题,JavaScript将其设置为了Function自己的原型,即下图:

同样在控制台验证一下:

补全其他关联

那么至此,整张原型链应该是这样的:

当然,我们还需要继续完善一下,比如说,

  1. 最顶部的Function的原型prototype是如何创建的,它的__proto__又指向哪呢?
  2. A方法又是如何创建,它的__proto__又指向哪呢?

Function.prototype

Function.prototype说白了还是个对象嘛,因此还是通过Object构造函数new出来的,那既然通过Object new出来,它的__proto__自然指向Object的原型了,那么我们可以在上图增加两条线

控制台验证一下:

A方法

函数自然通过Function new创建,那既然如此,A方法的__proto__指向的就是Function的原型对象了,继续在上图新增两条线

控制台验证一下:

最终图

于是最终的图就长这样:

emm看起来可能很乱,但跟着上述步骤一步步画下来,还是比较清晰的吧,大家也可以自己尝试手绘一下。