JS面向对象原理(二)------图解原型链(详)
引言
上周晚上有个朋友让我给她讲关于原型链的知识,我以为这都是小菜一碟,但是一番讲解下来才发现原来我熟悉的也不过是原型链的一小部分,当往上讲到Function和Object以及内置构造函数的时候,就有些牵强了。于是打算写一系列关于原型链这部分的内容。那就写吧,那要从哪里开始写起呢?说原型链,我想到的是为什么会有原型链,什么是原型链,利用原型链我们可以做什么。所以这系列的内容也会按照这个顺序来写。
为什么会有原型链:请移步到JS面向对象原理之面向对象编程发展史。
本章则讲述什么是原型链。
JS对象
为了能够清楚的解释这一切,我先从对象讲起。
JS中的数据类型分为两大类,一类是基本类型:String、Number、Boolean、Symbol、null、undefined,另一类是引用类型:Object、Array、Function。JS中有句话叫‘一切皆对象’,你可以把对象理解为像map那样的数据结构,也可以理解为一张散列表,总之,在对象中数据是以key:value的形式存储。
对象三要素:唯一标志,状态,行为
著名的哲学问题 “忒修斯之船”是一种有关身份更替的悖论:如果忒修斯的船上的木头被逐渐替换,直到所有的木头都不是原来的木头,那这艘船还是原来的那艘船吗。所以对象的唯一标志就起作用了,在JS中对象的唯一标志是内存地址。对象要有状态,状态是可以被改变的,改变就是行为。这样对象的三要素就成立了。
函数实际上也是对象。那基本类型也是对象吗?
从上图分析:
1.定义String数据有两种方式,使用JS内置的构造函数String
或者使用字面量来定义。
2.如果使用构造函数来定义,则会生成一个String对象,typeof a===object
。可以调用对象中存储的length
方法,也可以调用其__proto__中存储的原型对象中的方法如toUpperCase()
。
3.如果使用字面量定义,则生成一个原始值(PrimitiveValue),typeof b===string
。原始值中并没有定义任何的方法。但是依旧可以使用length
方法和toUpperCase
方法,为什么呢?
这是因为虽然基本数据类型不是对象,在存储形式上它被存放在栈中,但是在一定条件下(new关键字或者调用基本数据类型方法时)JavaScript 引擎会自动将原始类型的值转为包装对象实例(undefine和null除外),并在使用后立刻销毁实例。这就是原始类型的‘包装对象’,在上节中‘new语法糖为我们做了什么’部分有提到过关于包装对象的知识。
JS原型对象
我们现在知道了什么是对象了,那什么是原型对象呢?JS面向对象原理之面向对象编程发展史里我总结了一句话:原型对象是被设计来存放类的公有属性和方法的,自定义构造函数的原型除了自带一个不可枚举的constructor属性指向函数本身外,和其他空对象并无不同,而所有内置构造函数原型上还另外定义了实例方法,如Array.prototype.sort()
。你可以把原型对象理解为一个共享仓库。当然你如果觉得这句话还是很抽象的话,我再给大家举个栗子吧。
在自然界中,万物皆对象。那么我们用‘对象’来表示万物。万物又分为动物和植物,这叫分类。动物又分为人类,鱼类…这就是更加详细的类的划分了。在人类中有2个人,一个叫CSS,一个叫CHH。她们是两个长得一模一样的人,俗称双胞胎。有一天CSS走在路上不小心摔了一个大跟斗,脚摔断了,可是CHH却安然无恙,因为虽然她们两个长得一模一样,但是却是两个独立的对象,互相不受影响。那这跟原型对象有什么关系呢?现在假设有两条鱼,一条叫CSS,一条叫CHH,这两条鱼也是双胞胎。那我怎么区分这个CSS是鱼,还是人呢?其实我们只要分别把鱼类和人类的公共特征提取出来,放到一个对象中去描述(事实上的确就是这么做的)。这样我们只要看这个CSS符合的是哪一类的特征就知道她是鱼还是人了。那么这个用于存放公共特征的对象就叫原型对象。
也就是说在JS中有一些对象叫原型
typeof Object.prototype==='object'//true
Object的原型是Object.prototype,存放Object’类’的公共属性和方法。
Function的原型是Function.prototype,存放Function’类’的公共属性和方法
Person的原型是Person.prototype,存放Person’类’的公共属性和方法
…
JS构造函数之父Function与对象之父Object的纠葛
我们知道JavaScript是一种基于对象的语言。在JavaScript中,默认情况下,所有引用类型都继承自Object(通过原型链实现),任何函数的默认原型都是一个Object的实例。而所有构造函数的爸爸都是Function。包括在JS中九个原生(或内置)对象构造函数,都是Function的实例。
看到红色箭头指向的两行了吗,Function是function类型好说,为什么Object也是function类型呢?其实这也不难理解,毕竟Object实际上也是个构造函数f Object(){[navtie code]}
。实际上,Object被设定为函数是因为Object需要创建更多实例:
那有人又会问了:
你看这个Function instanceof Object
,Object instanceof Function
,字面意思看来是不是说这两个互为彼此的实例啊?
我先回答一下这个问题:Object的确是Function的实例,实际上它是个构造函数嘛。但是Function并不是Object的实例,只不过Function的原型是Object实例。Function作为一个构造函数之父,它自己实例化了自己。Object作为一个对象之父,它构造了所有的对象,包括原型。而instanceof
的判断依据是:只要实例的原型链中包含了某构造函数的原型,则instanceof
对该构造函数返回true
。
LOOK THIS PICTURE:
a是我们创建的类,叫a类,实际上它是一个构造函数,所以typeof a===‘function’。b是a类的实例,实际上它是一个对象,再结合new的机制来看,可以以为,Function是赋予JS运行能力的工厂,Object则是为生产线提供了原料(容器)。
最后一句话概括。
Object和Function都是JavaScript的基类,JavaScript中所有的构造函数都需要Function来实例化,包括Object。JavaScript中所有的构造函数的原型都需要Object来实例化。
JS原型链
原型讲的差不多了,那原型链是什么,ECMA-262把原型链定义为ECMAScript的主要继承方式。其基本思想就是通过原型继承多个引用类型的属性和方法。再通俗一点来说,原型链就是把原型链起来的链子。
原型链三大规则
1.构造函数的prototype指向其原型对象
2.原型对象的constructor指向构造函数本身
3.实例对象的__proto__指向构造函数的原型对象
也就是说构造函数和实例之间并没有直接的联系。和实例之间有直接联系的是构造函数的实例对象。
根据原型链的三大规则,我们得到了原型链的基本结构图。
那如果原型是另一个构造函数的实例呢?那就意味着这个原型本身内部有个__proto__指向另一个原型,相应地,另一个原型也有一个constructor指向另一个构造函数。这样就在实例和原型之间构造了一条原型链,这就是原型链的基本思想。查找实例属性时按照绿色的线路进行查找。
原型搜索机制:原型链的存在拓展了原型的搜索机制,当访问实例对象的某个属性的时候,如果存在该属性,就返回该属性的值,否则,将自动查找该对象的__proto__ 指向的原型对象中是否存在该属性。如果存在,就返回该属性的值,否则,继续在原型对象的__proto__指向的原型对象中寻找。这样一直查下去,直到找到Object的原型对象,如果还没找到此属性,则返回undefined。
其实原型链到这里也就应该差不多了,再加上JS的两个顶层构造函数也就该完整了。结合上段对Function和Object的关系阐述以及原型链的三大规则,得到了Function和Object的原型链图解:
1.Function实例化了自己,所以Function是自己的实例,该实例的__proto__指向自己的原型对象。
2.Object实例化了自己的原型对象,所以Object.prototype是Object的实例,但是Object.prototype并没有指向自己是因为如果指向自己原型链将没有终点,原型链的搜索将会不断在Object.prototype中转圈圈,所以JS设定Object的原型对象的__proto__为null标志原型链的终点。
3.Function的原型对象是Object的实例,所以Function.prototype.__proto指向Object.prototype。
最后上张全家福—完整的原型链图:
- 下章讲解利用原型链可以做什么以及怎么做。
本文地址:https://blog.csdn.net/qq_34666266/article/details/109612696