0
点赞
收藏
分享

微信扫一扫

【JS】1-原型继承的原理解析

科牛 2022-08-16 阅读 97

在JavaScript当中,对象A如果要继承对象B的属性和方法,那么只要将对象B放到对象A的原型链上即可。而某个对象的原型链,就是由该对象开始,通过​​__proto__​​​属性连接起来的一串对象。​​__proto__​​​属性是JavaScript对象中的内部属性,任何JavaScript对象,包括我们自己构建的对象,JavaScript的​​built-in​​对象,任何函数(在JavaScript当中,函数也是对象)都具有这个属性。如下图就是一个原型链的例子:

【JS】1-原型继承的原理解析_javascript

上图中,A,B,C分别代表3个对象,蓝色箭头串接起来的所有对象就构成了对象C的原型链,其中C的​​_proto__​​​属性指向B,B的​​__proto__​​​属性指向A,A的​​__proto__​​​属性可能指向更高层的对象,也可能指向​​null​​(表示A不继承任何对象的属性和方法)。

如果我们引用了C的某个属性或者方法,那么JavaScript就会顺着C的原型链进行查找,即首先查找对象C本身,看所引用的属性名或者方法名是否存在,如果存在就停止查找直接返回,如果不存在,就通过C的​​__proto__​​​属性找到原型链中的B对象,继续在B对象中查找,如果B对象中找到所引用的属性名或者方法名,那么就停止查找直接返回,如果B对象中也不存在,就通过对象B的​​__proto__​​​属性找到原型链中的A对象,继续重复上述查找过程,直到找到所引用的属性或者方法为止(同时也可能查找完对象C的整个原型链也没有找到所引用的属性或者方法,那么该属性或者方法就是​​undefined​​的)。

因此,只要能够成功的为某一个对象构造出我们需要的原型链,那么就能让该对象继承我们想要它继承的方法或者属性。而想要成功构造对象的原型链,就还必须理解​​prototype​​属性,JavaScript当中已经存在的原型链,以及当我们创建对象时,原型链被构造的过程。

prototype属性

prototype属性存在于JavaScript的任何函数当中,这个属性指向的对象就是所谓的原型对象,在构造原型链时需要原型对象。

JavaScript当中已经存在的原型链

在JavaScript当中存在​​Object​​​,​​Function​​​,​​Array​​​,​​String​​​,​​Boolean​​​,​​Number​​​,​​Date​​​,​​Error​​​,​​RegExp​​这9个built-in函数,一个​​built-in​​​的​​Math对象​​​,通过这上述9个​​built-in函数​​​我们可以创建相应的对象,同时,这9个​​built-in函数​​​的​​prototype​​​属性所指向的原型对象也是​​built-in​​​的。下面的图示解释了这几个函数以及各自​​prototype​​属性所指向的原型对象之间的关系

【JS】1-原型继承的原理解析_原型链_02

(如果此图看不清,可打开 http://p3nqtyvgo.bkt.clouddn.com/20180927_02.png 下载 )

  上面的图示中,黄色方框代表​​built-in函数对象​​,深绿色方框代表​​built-in函数​​​的​​prototype属性​​​指向的原型对象,名字都叫​​xx prototype object​​,浅绿色方框(即Math对象)代表普通对象,蓝色箭头连接非built-in函数对象(无论是普通对象如Math,还是原型对象)的​​__proto__​​​属性,而土黄色箭头连接函数对象的​​__proto__​​属性。

  通过上图可以发现,所有built-in函数对象的原型链最终都指向​​Function prototype object​​,所有非函数对象的原型链最终都指向​​Object prototype object​​​,并且​​Function prototype object​​​的​​__proto__​​​属性也指向​​Object prototype object​​​,​​Object prototype object​​​的​​__proto__​​​属性指向为​​null​​。

因此,​​Object prototype object​​​是所有原型链的顶端,通过原型链查找规则可知,所有​​built-in​​​函数对象同时继承了​​Object prototype object​​​和​​Function prototype object​​​上的属性和方法,而所有非built-in函数对象只继承了​​Object prototype object​​​上的方法。​​Function prototype object​​​包含了所有函数共享的属性和方法,而​​Object prototype object​​包含了所有对象都共享额属性和方法。

对于上图中原型对象包含的constructor属性,下文当中有解释。

创建对象时,原型链的构造过程

在JavaScript当中创建对象有2中方式,一种是通过定义函数使用new方法来构造,另一种是使用对象字面量的方式,即:

var
"Jim Green"
};

使用这两种方式创建对象时,对象的原型链构造过程有所不同。

  • 1. 使用函数的方式构造对象

使用函数的方式构造对象分为两步:首先需要定义一个函数作为构造函数,然后使用new方法构造对象。接下来就来看一下这两个步骤会发生什么。

假设我们定义了一个函数名为​​F​​​,此时JavaScript会为我们做两件事,第一:根据我们定义的函数创建一个函数对象,第二,设置这个函数的​​__proto__​​​属性和​​prototype​​​属性。其中​​__proto__​​​属性指向​​built-in​​​的​​Function prototype object​​​,而​​prototype​​​属性指向一个为函数对象F新创建的原型对象,这个新创建的原型对象通过调用​​new Object()​​​构造出来,并且为这个新创建的对象添加​​constructor​​​属性,该属性指向函数对象​​F​​。最后的结果如下图所示:

【JS】1-原型继承的原理解析_原型链_03

上图中为了方便,没有画出​​Function prototype object​​​和​​Object prototype object​​​的​​constructor​​​属性。而​​F​​​ ​​prototype object​​​的​​__proto__​​​属性为何指向​​Object prototype object​​,下文介绍new操作符时有解释。

当我们使用new方法调用F函数的时候,JavaScript也会为我们做两件事,第一,分配内存作为新创建的对象,第二,将新创建的对象的proto属性指向函数F的原型对象,结果如下图:

【JS】1-原型继承的原理解析_javascript_04


上图中,​​obj​​就是调用new方法通过​​函数F​​​创建出来的对象,我们可以看到对象obj的原型链包含了​​函数F​​​的原型对象,以及​​Object prototype object​​​,这样,对象​​obj​​​通过原型链查找规则,就能继承​​函数F​​​的原型对象,以及​​Object prototype object​​​上面定义的属性和方法了。并且如果我们想知道一个对象是由哪个方法构建的,只需要访问这个对象的​​constructor​​​属性即可,上例中,只要我们访问​​obj.constructor​​​,那么就知道obj是由​​函数F​​​创建的。同时,由于​​F​​​ ​​prototype object​​​上文中介绍是由​​new Object​​​函数创建的,根据此处介绍,F ​​prototype object​​​的​​__proto__​​​属性应该指向​​Object函数​​​的原型对象,即​​Object prototype object​​。

  • 2. 使用对象字面量定义对象

当使用对象字面量创建对象时,JavaScript会为我们做两件事:

1 分配内存作为新创建的对象。
2 将新创建对象的​​​__proto__​​​属性指向​​Object prototype object​​。

结果如下图所示:

【JS】1-原型继承的原理解析_原型链_05

上图为了简化,同样没有画出​​Object prototype object​​​的​​constructor​​属性

继承

理解了上面所讲的原理之后,假设目前有一个对象​​A​​​(这个对象可以是任意的,包括JavaScript built-in的对象,任何函数对象,任何原型对象,以及我们自己new出来的对象),现在想创建一个对象​​obj​​​,让​​obj​​​继承​​A​​的属性和方法。

通过上面的介绍,我们知道创建对象有两种方式,但是使用对象字面量创建的对象其原型链总是只包含两个对象,一个是其自己,一个是​​Object prototype object​​​,根本不可能包含对象​​A​​​,无法达到让对象​​obj​​​继承对象​​A​​属性和方法的效果。因此,只能使用函数的方式创建对象,让对象A包含在新创建对象obj的原型链中即可。

根据上面的讲解,如果是用函数的方式创建对象,那么在调用​​new​​​方法时,新创建对象的​​__proto__​​​属性会指向函数的原型对象。因此,只要在调用函数之前,将函数的原型对象换成​​A​​​,然后再调用​​new​​​方法,就可以将对象A包含在新创建的对象​​obj​​​的原型链中,这样通过原型链查找规则,​​obj​​​就继承了A的属性和方法。假设用来创建对象​​obj​​​的函数为​​B​​,则相关代码为:

B.prototype = A;
B.prototype.constructor = B;
var obj = new

上面代码中的​​B.prototype.constructor = B​​​,是因为对象A中可能没有​​constructor​​​属性,或者​​constructor​​​属性不指向​​B​​​,而为了方便通过访问任何由​​B​​​函数创建的对象的​​constructor​​​属性,就可以正确的知道该对象是使用函数​​B​​构造出来的。相关图示如下图:

【JS】1-原型继承的原理解析_原型链_06

上图中虚线框所包围的​​B prototype object​​​就是定义函数​​B​​​时,JavaScript为函数B生成的原型对象,但是该对象被我们用代码替换成了对象A。由于这个被替换的​​B prototype object​​没有其他地方再用到,因此会被回收掉。


举报

相关推荐

0 条评论