您的位置:首页 > Web前端 > JavaScript

[JavaScript]继承的真真假假

2016-05-10 14:10 519 查看
文章结构继承的判断标准
真真假假的继承实现方式构造函数绑定
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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: