标签:
文章结构
JavaScript的继承实现设计得有点遮遮掩掩,对于从强类型语言转向来学习JavaScript的新手来说,是件很费脑瓜子的事情。
Sodino作为从Java转向JavaScript的新学员,尝试用这篇文章来理清‘继承’这点事。
考虑到JavaScript已经实现了’instanceof’这个运算符,所以本文中约定如下判断标准:
|
|
当chlid instancof Parent值为true时,才判定Child继承自Parent。
在此判断标准下,来看看以下各种“百花齐放”的继承实现方式吧…操家伙,割韭菜。
在各种实现方式分为两种思路:
增加Child的属性、方法
操作prototype实现继承关系
prototype拷贝prototypeprototype模式下面逐一细说各种方式的实现与结论判断。
可以使用Function的apply()、call()、bind()来绑定构造函数,实现所谓的’继承’效果。
如下代码,child可以执行在Parent类中定义的play()方法。
|
|
代码运行如下:

使用构造函数绑定的方式,对于Chlid()构造函数来说,相当于借用了Parent()函数内的内容来对Child进行属性或方法的定义,在本例中是新增加了play()方法。
与下面的代码是等价的。
|
|
应该知道instancof的运算原理是和对象的原型链相关的,所以构造函数绑定的方式并没有将Parent与Child在原型链上建立关系。代码运行后child instancof Parent值是false!!!
所以这种方式只是代码复用的一种技巧,看起来是’继承‘,是假’继承‘。
这种实现方式是将Parent.prototpye中的属性、方法全部复制到Child中去。
实现如下:
|
|
代码运行如下:
很明显,由于extendByCopy()只是将两个类的prototype经复制后看起来一模一样,但并没有真正在Child的原型链建立与Parent的关系,所以child instanceof Parent值仍为false,所以这也是一种假的’继承‘实现方法。
直接继承prototype的方法是将Parent.prototype赋值到Child.prototype,使两者的prototype是一致的。
如下代码中,Child.prototype指向一个新对象,但由于每个prototype都有一个constructor属性,指向它的构造函数,当执行了Child.prototype = Parent.prototype后,Child.prototype.constructor将会等于Parent,会导致后续通过Child()构造函数初始化的对象的constructor都会是Parent(),这显然会是继承链的紊乱。
所以必须手动纠正,将Child.prototype.constructor赋值为Child本身,以此解决。
这也是JavaScript中务必要遵守的一点,如果替换了prototype对象,则下一步必然是为新的prototype对象加上constructor属性并指回原来的构造函数。
代码实现如下:
|
|
运行输出如下图:
这种方式看似符合文章开头对’继承的判断标准’。但真的是‘继承’吗?很明显该方式有以下缺点:
第一继承关系紊乱了。
child instanceof Parent值为true是正常的,但parent instanceof Child值也为true,这…‘乱伦’的画面感不敢看。
第二,由于示例代码中 play()方法并没有声明在Parent.prototype中,所以Child的对象也无法直接调用该方法。
第三,两者的prototype一致了,会导致对任一prototype的改动都会同时反馈在Chlid和Parent上,而这是不严谨的编程思想。(虽然严谨也不是JavaScript的风格,JavaScript一直都是随随便便的)
第四,在debug界面查看Child的原型链,发现其不完整,缺少了Parent这一环了;而且Parent也被指向了Child,会导致后续调bug时干扰分析思路。

所以’直接继承prototype’方式,虽然满足child instanceof Parent == true,但这种代码技巧更像是一种‘变脸易容’而已,Sodino也把该方式归为假继承。
prototype模式是对上文直接继承prototype的改进,指将子类的prototype对象指向一个父类的实例。
|
|
运行后代码如下所示:

终于child instanceof Parent值为true了。这是一种真正的继承实现方式。
可以在debug界面上观察该child对象的原型链如下图所示:

相比上文的直接继承prototype,Parent的原型链并没有被改变,而且子类的原型链从Child指向Parent再指向Object!很完美!
上文prototype模式已经完美实现继承了。但从代码设计层面上来看,JavaScript中,prototype中声明的属性、方法是共用、共享的,这部分数据被子类是继承是没有问题的。
但父类也有一些自己定义的私有属性、方法,如代码中的play()方法,在JavaScript语言层面上,它并没有定义在Parent.prototype中,所以能不能在实现继承的同时保留该方法仍是父类的私有方法,子类不可访问吗?
答案是可以的。上文prototype模式使用了Parent的一个实例对象,由于该实例对象中有play()方法,所以JavaScript解释器在执行chlid.play()时,发现child本身并没有定义,会顺着原型链逐级向上查找直至找到或找不到抛出异常。在本文示例中,很方便就在Child.prototype,即new Parent()的这个对象中找到了该方法并执行。
所以做出的改进要保留不变的是Child.prototype仍然通过一个对象间接指向Parent.prototype,需要做出改变的是该对象是个空对象即可。
具体实现为Child.prototype指向一个空的构造函数,但该空的构造函数原型指向Parent.prototype即可。
|
|
运行后效果如下图。

查看child与parent的原型链,仍旧很完美。

所以这是一种更严格的继承实现方式。
标签:
原文地址:http://blog.csdn.net/sodino/article/details/51362762