原型链,从来都没去理解过,为了面试特意查了一下,发现早就是在工作里边用的熟悉了,只是我没有去深入了解这块。
正文
工作中,为了调试,我们常常输出对象查看内容。
例如,我在vue中输出内容,经常会发现会经常看到Prototype,__proto__,.constructor等意义不明的东西。
后来工作久了,细细查了一下,才知道这玩意是原型链。
毕竟我不是正规前端出身,做事儿总是奉行能用就行,所以后来一直没详细整理这块的内容。
如今闲下来了,针对这块内容,这里梳理一下,也防止面试被问到。
必要概念
在正式进入原型链之前,先简单了解一些简单的概念。
构造函数
构造函数和普通函数本质上没什么区别,只不过使用了new
关键字创建对象的函数,被叫做了构造函数。
构造函数命名通常采用首字母大写的方式,以便与普通函数和变量进行区分。
1 | function Person(name, age) { |
Object
在JavaScript中,Object
构造函数创建一个对象包装器。
如果给定值是null或undefined,将会创建并返回一个空对象,否则,将返回一个与给定值对应类型的对象。
当以非构造函数(即,没有使用new)的方式被调用时,Object函数将转换为对象。
它可以将任何值转换为对象,这种方式通常被用于将基本数据类型(如数字、字符串和布尔值)转换为相应的对象。
以下代码举例,可以看出Object的转换能力。
1 | let num = 123; |
根据上面构造函数的定义,Object
是满足当做一个构造函数的要求的。
既然是个函数了,那么就会有prototype
属性。
这里先埋一个伏笔,到现在只需要记住,Object是JS运行时就创建好了的,是JS内置的,并且Object身上有个prototype
属性即可
Function
Function是一个特殊的构造函数,它是在JavaScript运行时就创建的一个对象。
Function是所有函数的构造函数,先通过代码的方式举例一下通过new Function()的方式创建一个函数。
1 | const sum = new Function('a', 'b', 'return a + b;'); |
到这里我们知道了new Function()
构造函数可以动态地创建函数,从这里先解释一下上面有关Object的解释中的伏笔。
既然所有函数都是Function生产出来的,那么Object这个构造函数是不是也是Function生产出来的呢?
答案是的。
那Function自身怎么来的呢?
答案是Function创造了Function,没错这是一个特殊的情况,因为万物都有个源头,Function和Object一样,都是JS在运行时就创建好了。
例如下面代码,可以先略过。看完下面有关constructor 的解释再回来看这个就理解了。
1 | function abc () {} |
这段代码就印证了,所有函数都是Function生产的(包括Function自身也是)
显式原型(prototype)
显示原型就是利用prototype属性查找原型,只是这个是函数类型数据的属性。
当然,这个东西在我们那个时候叫显示原型,现在叫做原型对象,本质上是一个东西。
个人感觉现在的说法更贴切,因为这东西本质上指向的内容是对象,所以叫做原型对象,更贴切。
- Prototype是函数的一个属性
- 是个对象
- 创建函数的时候时候,自带该属性
隐式原型(__proto__)
隐式原型是利用__proto__属性查找原型,这个属性指向当前对象的构造函数的原型对象
1 | console.log(per1.__proto__ === Person.prototype); // true |
- 对象的属性
- 指向构造函数Prototype
构造器(Constructor)
对于原型对象Prototype来说,它有个constructor属性,指向它的构造函数。
1 | console.log(Object.\__proto__ === Function.prototype) // 结果为true |
经典图解
在了解了上述的名词之后,我们再看下边的这张图,应该就会清晰很多了。
以下这张图,代入核心知识点。
为了清晰的解释这个关系,我们先在脑海中抽象出两个线路,一个是函数线,一个对象线。
这两个线,只是暂时的把函数和对象两个概念做个区分,但实际上,函数属于对象,只是一个特殊的对象而已。
在JS中一切皆对象,而所有的对象原型最终指向的是null
1 | 牢记下面我抽象出来的两个知识点 |
函数有 prototype
和 __proto__
, 对象有 __proto__
。
写一段伪代码,只是帮助你理解原型链,但是这个核是一定要记住的。
1 | Function = { |
个人拓展
之前看到有人在群里装逼,说隐式原型(__proto__)已经被废止,说已经使用新的[[Prototype]]做标识,听他说的信誓旦旦,我还真信了。
后来查了一下MDN的相关文档,大致闹明白了是怎么回事儿。
[[Prototype]] 是什么
ECMA-262使用一些内部特性来描述属性的特征,这里放一下官方文档的原文。
1 | 遵循 ECMAScript 标准,符号 someObject.[[Prototype]] 用于标识 someObject 的原型。 |
这段话说的挺绕,但实际就很简单的意思:这些特性是由为JavaScript实现引擎的规范定义的,因此,开发者不能在JavaScript中直接访问这些特性。
为了将某个特性标识为内部特性,规范会用两个中括号把特性的名称括起来,比如[[Prototype]]、[[Enumerable]]等。
[[Prototype]]就是这个一个内部属性, 它指的是对象的原型,脚本中没有访问这个[[Prototype]]特性的标准方式,但Firefox、Safari和Chrome会在每个对象上暴露__proto__
属性,通过这个属性可以访问对象的原型。
因此,所谓的__proto__
被废除,纯粹是瞎说,我亲自在控制台上敲过,用的很正常。
而[[Prototype]] ,也只是内部属性,并不能在控制台中被执行。
所以,原型链这种基础的东西,几乎不太可能动的,有些人纯粹的是胡编的。
结语
虽然很绕,但是其实一旦理解了就很容易明白,而明白之后,再看那张经典的原型链图就会非常明白。
B站的【前端八股文】原型和原型链,还有掘金的你可能不太理解的JavaScript - 原型与原型链这篇文档都解释的很不错,我算是照猫画虎的理解了一下。
当然,我也翻看了现代MDN的文档,发现似乎隐式原型(__proto__)被官方弃用了,现在似乎
只是,这块的知识,除非是涉及到基础框架开发,不然大多数时候是用不上的,属于一看就能明白是什么意思的基础问题,但开发根本用不到太多。