[JavaScript]继承的真真假假
2016-05-10 14:10
519 查看
文章结构继承的判断标准
真真假假的继承实现方式构造函数绑定
prototype的拷贝
直接继承prototype
prototype模式
利用空对象
JavaScript的继承实现设计得有点遮遮掩掩,对于从强类型语言转向来学习JavaScript的新手来说,是件很费脑瓜子的事情。
Sodino作为从Java转向JavaScript的新学员,尝试用这篇文章来理清‘继承’这点事。
当
操作
直接继承
利用空对象
下面逐一细说各种方式的实现与结论判断。
如下代码,child可以执行在Parent类中定义的
代码运行如下:
使用构造函数绑定的方式,对于
与下面的代码是等价的。
应该知道
所以这种方式只是代码复用的一种技巧,看起来是’继承‘,是假’继承‘。
实现如下:
代码运行如下:
很明显,由于
所以必须手动纠正,将
这也是JavaScript中务必要遵守的一点,如果替换了
运行输出如下图:
这种方式看似符合文章开头对’继承的判断标准’。但真的是‘继承’吗?很明显该方式有以下缺点:
第一继承关系紊乱了。
所以’直接继承prototype’方式,虽然满足
运行后代码如下所示:
终于
可以在debug界面上观察该
相比上文的
但父类也有一些自己定义的私有属性、方法,如代码中的
答案是可以的。上文
所以做出的改进要保留不变的是
具体实现为
运行后效果如下图。
查看
所以这是一种更严格的继承实现方式。About Sodino
真真假假的继承实现方式构造函数绑定
prototype的拷贝
直接继承prototype
prototype模式
利用空对象
JavaScript的继承实现设计得有点遮遮掩掩,对于从强类型语言转向来学习JavaScript的新手来说,是件很费脑瓜子的事情。
Sodino作为从Java转向JavaScript的新学员,尝试用这篇文章来理清‘继承’这点事。
继承的判断标准
考虑到JavaScript已经实现了’instanceof’这个运算符,所以本文中约定如下判断标准:1234567891011 | function Parent() {}function Child() {}// -------start------继承的各种实现方式// -------end------ var parent = new Parent();var child = new Child();chlid instancof Parent == true |
chlid instancof Parent值为true时,才判定
Child继承自
Parent。在此判断标准下,来看看以下各种“百花齐放”的继承实现方式吧…操家伙,割韭菜。
真真假假的继承实现方式
在各种实现方式分为两种思路:增加Child的属性、方法构造函数绑定
操作
prototype实现继承关系
prototype拷贝
直接继承
prototype
prototype模式
利用空对象
下面逐一细说各种方式的实现与结论判断。
构造函数绑定
可以使用Function的
apply()、
call()、
bind()来绑定构造函数,实现所谓的’继承’效果。
如下代码,child可以执行在Parent类中定义的
play()方法。
1234567891011121314151617181920 | 代码一:function Parent() { this.play = function() { console.log('play ...'); };}function Child(){ Parent.apply(this);}var parent = new Parent();var child = new Child();// true falseconsole.log(parent instanceof Parent, parent instanceof Child);// false trueconsole.log(child instanceof Parent, child instanceof Child);child.play(); // print 'play ...' |
使用构造函数绑定的方式,对于
Chlid()构造函数来说,相当于借用了
Parent()函数内的内容来对
Child进行属性或方法的定义,在本例中是新增加了
play()方法。
与下面的代码是等价的。
123456 | function Child() { // 借用了Parent()中的代码内容 this.play = function() { console.log('play ...'); };} |
instancof的运算原理是和对象的原型链相关的,所以构造函数绑定的方式并没有将
Parent与
Child在原型链上建立关系。代码运行后
child instancof Parent值是false!!!
所以这种方式只是代码复用的一种技巧,看起来是’继承‘,是假’继承‘。
prototype的拷贝
这种实现方式是将Parent.prototpye中的属性、方法全部复制到
Child中去。
实现如下:
123456789101112131415161718192021222324252627282930313233 | 代码二: function extendByCopy(Child, Parent) { var p = Parent.prototype; var c = Child.prototype; for (var i in p) { c[i] = p[i]; }}function Parent() { this.play = function() { console.log('play ...'); };}function Child(){} extendByCopy(Child, Parent);var parent = new Parent();var child = new Child();// true falseconsole.log(parent instanceof Parent, parent instanceof Child);// false trueconsole.log(child instanceof Parent, child instanceof Child);// child.play() exception..// 因为extendByCopy()只是修改prototype// 并没有将Parent私有的方法也复制给Chlid //sodion.comchild.play(); |
很明显,由于
extendByCopy()只是将两个类的
prototype经复制后看起来一模一样,但并没有真正在
Child的原型链建立与
Parent的关系,所以
child instanceof Parent值仍为false,所以这也是一种假的’继承‘实现方法。
直接继承prototype
直接继承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属性并指回原来的构造函数。代码实现如下:
1234567891011121314151617181920212223 | function Parent() { this.play = function() { console.log('play ...'); };}function Child(){}Child.prototype = Parent.prototype; // Child.prototype指向新对象 // sodino.comChild.prototype.constructor = Child; // 必须恢复Child.prototype.constructor为Child本身,构造函数不能变var parent = new Parent();var child = new Child();// true true console.log(parent instanceof Parent, parent instanceof Child);// true true console.log(child instanceof Parent, child instanceof Child);child.play(); // exception... |
这种方式看似符合文章开头对’继承的判断标准’。但真的是‘继承’吗?很明显该方式有以下缺点:
第一继承关系紊乱了。
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的改进,指将子类的
prototype对象指向一个父类的实例。
12345678910111213141516171819202122 | 代码三:function Parent() { this.play = function() { console.log('play ...'); };}function Child(){}Child.prototype = new Parent(); // 子类的prototype对象指向一个父类的实例。Child.prototype.constructor = Child; // 修正Child的构造函数var parent = new Parent();var child = new Child();// true falseconsole.log(parent instanceof Parent, parent instanceof Child);// true trueconsole.log(child instanceof Parent, child instanceof Child);child.play(); // print play... |
终于
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即可。
12345678910111213141516171819202122232425262728 | function extend(Child, Parent) { var F = function(){}; F.prototype = Parent.prototype; Child.prototype = new F(); Child.prototype.constructor = Child;}function Parent() { this.play = function() { console.log('play ...'); };}function Child(){} // sodino.comextend(Child, Parent);var parent = new Parent();var child = new Child();// true falseconsole.log(parent instanceof Parent, parent instanceof Child);// true trueconsole.log(child instanceof Parent, child instanceof Child);// exception....child.play(); |
查看
child与
parent的原型链,仍旧很完美。
所以这是一种更严格的继承实现方式。About Sodino
相关文章推荐
- jsp九大内置对象
- jstl
- html table动态合并单元格 js方法
- JS事件
- 一道非常有趣的JS题
- 原生js文字标签云上下滚动播放
- javascript第一天数据类型及声明方式
- 子页面控制父页面跳转
- javascript实现的猜数小游戏完整实例代码
- js实现的简单图片浮动效果完整实例
- 深入理解js匿名函数与闭包
- ExtJS快速入门指南
- js 获取事件源
- js/jsp操作cookie的方法
- 详情页JS特效:往下滚动时,侧边新闻随着固定显示
- js学习笔记2---HTML属性操作
- javascript学习——使用javascript
- js事件多次绑定问题
- JavaScript开发之路01(初识Sencha Touch框架)
- js学习笔记1---使用方法