在JavaScript
中,原型(
prototype
)
和原型链(
prototype chain
)
是非常重要的概念,但是很多人对它们的掌握都有点不太清楚,本文带大家彻底搞懂它们!
首先我们需要搞清楚几个非常重要的基本概念:
所有的对象都是通过new
函数
创建的,且所有的对象都是引用类型
如上,字面量{}
只是创建对象的一种语法糖,其实上面的两种写法是完全等价的。而Object
其实就是一个js
内置的构造函数。
所有的函数也都是对象,都是通过new Function
创建的(可以称为函数对象)
既然函数也都是对象。那么函数又是怎么创建的呢?其实函数都是通过new Function创建的,而且所有的对象都是通过new 函数创建的,又称为函数对象。
如上。同理可得,上面的两种写法也是完全等价的。而Object既然是一个函数、它其实就是相当于var Object = new Function(),所以它也不例外。
思考:其实Function
也是一个函数对象,那它又是怎么创建的呢?
结论:Function函数是JS内置的,不需要创建!
综上所述,可以用一张图来形象的展示出它们的关系:
prototype
什么是原型?
所有的函数都有一个属性叫prototype
,称之为函数原型
可以运行如上代码看看效果,Object
和Array
其实都是js
内置的构造函数。
默认情况下,prototype
是一个普通的object
对象
通过这个特点、这个后面会讲到,我们可以在原型链上查找实例化后的对象所对应的构造函数。
__proto__
什么是隐式原型?
所有的对象都有一个属性叫__proto__
,称之为隐式原型
可以运行如上代码看看效果,所有的对象在被创建的时候就会有一个叫__proto__
的属性。
默认情况下,__proto__
指向创建该对象的函数的原型,即prototype
运行如上代码,可以验证结论。在这里其实就已经初步形成了一种三角关系了。
prototype chain
到重点了,上面讲了原型和隐式原型,估计有的人会问:搞这么麻烦,它们有什么用呢?
如上代码,我通过构造函数User实例化了两个对象user1和user2,所以user1和user2的sayHello方法是不相等的,因为它们都有各自属性和方法。
但是我们可以发现。完全没必要创建多次,它们的方法其实是一样的。可以假设一下。如果我们创建了很多个示例,那么这样就会浪费内存空间。
现在让我们来改下代码
先看结果,这时候我们会发现,代码依然能够如期运行,而且user1
和user2
的sayHello
方法是完全相等的!
那为什么user1
和user2
中并没有sayHello
这个属性,但是却能够运行呢?其实这就是原型链
的强大之处。
那这个到底是怎么实现的呢?原型链它到底是什么呢?
运行如上代码,需要理解以下几点:
首先根据上面所说的,user1是个实例化的对象,那么user1.proto一定是指向User.prototype
那User.prototype其实也是个对象,既然是对象就有proto属性,那么User.prototype.proto一定是指向Object.prototype,也可以通过user1.proto.proto来访问,即原型对象的原型对象
同理,那user1.proto.proto也是个对象,也有proto属性,不过这里就指向null了
因为每个一对象都有隐式原型,隐式原型的指向就形成了一个链条,称之为原型链
所以上面的示例就会按照原型链继续依次查找
当我们访问一个对象的成员时:
首先看该对象自身是否拥有该成员,如果有直接使用;
如果没有再看该对象的隐式原型是否拥有该成员,如果有直接使用;
如果还没有就会继续查找原型对象的原型对象(该对象的隐式原型所指向的原型对象),直到找到为止;
这样形成的链条就被称为原型链(prototype chain)。
读到这里,再看上面的问题,为什么实例对象user1
和user2
中并没有sayHello
这个方法,但是却能够运行?答案就是可以在原型链上查找到sayHello
这个方法。
通过这种方法。我们就可以实现多个实例对象共享一个原型对象,这样就可以极大的减少对内存空间的消耗且极大的提升了代码的可复用性。这就是原型链
的作用。(其实这也是js
实现继承
的基本原理,这里就不展开说了)
原型链
的全貌下面就是js
中原型链
的全貌了
这里有两个比较特殊的点需要注意一下:
Object
的prototype
的__protp__
指向null
Function
的__protp__
指向自身的prototype
仔细对照这张图,你是否能够理解如下问题:
为什么任意一个对象都能调用toString和hasOwnProperty方法了吗?
为什么任意一个函数都能调用call和toString方法了吗?
为什么任意一个数组都能调用自身没有的api了吗,如filter,push等方法了吗?
其实答案都是因为原型上有!
如果你能够看到这里并且完全理解了,那么其实你已经对原型和原型链已经有了一个比较全面的认知了。
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流