继承通过原型链来实现,我们知道,构造函数的prototype属性指向构造函数原型对象,如果为该对象添加成员,就能够实现实例之间的共享,那么,如果将原型指向另一个对象,就可以拥有该对象的所有成员。
这就是所谓的继承,继承链并非单一,理论上来讲,能够无限的继承下去, 也就是我们常说的原型链
即:
a.prototype= new b();
b.prototype=new c();
这样,a不仅拥有了b的所有成员,包括实例成员,还包含了c的所有成员。
之所以能够实现继承,是因为如果在调用对象成员时,如果找不到该成员,就会在原型中继续找,而原型又是另一个对象的实例,那么会继续找下去,直到找到为止,如果没有找到,就报错。
1.原型继承:
function superCon(name,age){ this.name=name; this.age=age; } superCon.prototype.showinfo=function() { alert(this.name+this.age); } function sub(){} sub.prototype= new superCon('moersing',18); var sub1 = new sub(); sub1.showinfo(); //moersing18
上述代码实现了从 sub继承superCon的过程,咋一看没什么问题,但是,但父类在拥有引用成员的时候,就会出问题了。
function superCon(name,age){ this.name=name; this.age=age; this.books=['c#','vb.net','javascript']; } superCon.prototype.showbooks=function(){ this.books.forEach(function(c,i,a){document.write(c);})} function sub(){} sub.prototype= new superCon(); var sub1 = new sub('moersing',18); sub1.books.push('css3'); sub1.showbooks(); //分别输出,'c#','vb.net','javascript','css3'; var sub2 = new sub(); sub2.showbooks(); //分别输出,'c#','vb.net','javascript','css3';
我们希望sub2拥有单独的一个books数组,但是事实却不是这样的,原因还是 原型共享 问题,由于sub.prototype指向了superCon的实例,也就是说,相当于在sub.prototype中定义了一个books数组,根据原型共享的概念,通过实例改变原型中的引用类型,那么,在其他的实例中也会反映出来,为了解决这个问题,可以使用另一种继承方式,那就是 组合继承。
2.组合继承
function superCon(name,age){ this.name=name; this.age=age; this.books=['c#','vb.net','javascript']; } superCon.prototype.showbooks=function(){ this.books.forEach(function(c,i,a){document.write(c);})}
function sub(){ superCon.call(this,['moersing',18]); } sub.prototype= new superCon(); var sub1 = new sub(); sub1.books.push('css3'); sub1.showbooks(); //分别输出,'c#','vb.net','javascript','css3'; var sub2 = new sub(); sub2.showbooks(); //分别输出,'c#','vb.net','javascript';
代码差不多,但是解决了共享的问题,在sub函数中调用了superCon的call方法,将this传进去,并给了两个参数。
有人可能会迷糊,但是原理很简单,利用call来改变父类构造函数的运行环境(应该说活动对象上下文),而这个环境就是this。那么,this是谁?
就是new sub();也就是sub对象的实例,说简单点就是,sub.prototype是指向了superCon的实例没错,但是,在call的时候,sub借用了super重新创建了实例成员(name,age,books),也就覆盖了prototype 中的name,age和books。
那么一切就顺理成章了:
1.sub.prototype指向superCon的实例,继承了superCon所有成员。
2.在new sub()的时候,借用了父类的构造函数(call调用),那么,子类的实例就拥有了父类所有实例成员,自然也就拥有了单独的books数组了。
这样就能解决继承中,原型共享的问题。
但是!!!不知各位发现没有??sub.prototype = new superCon(); 也就是说,sub的原型还是指向了superCon的实例,那么,sub原型就拥有:name,age,books和一个showbooks方法。
只不过在call的时候被覆盖掉了,通过一个实例可以验证出来:
delete sub1.books; sub.showbooks(); //分别输出,'c#','vb.net','javascript',少了一个'css3'。
我们通过使用delete 删除实例属性books,但是调用showbooks的使用,还是能够打印数组的内容,但是却是没有push之前的内容。
可以看出,实际上,在sub实例中拥有了name,age,books,而原型也有name,age,books,只是在访问的时候,会先访问实例中的而不是原型中的,所以,借用构造函数也并非没有缺点,需要创建两次实例成员。
这是目前业界用的最多的继承方式,如果你觉得别扭,可以尝试使用 道格拉斯▪克罗克福德提出的 "寄生组合式继承".
关于 道格拉斯▪克罗克福德 提出的原型式继承,本人不是很赞同,一来需要手动创建一个对象,二来,其原理就是拷贝该对象给另一个对象的原型,也就存在原型对象引用类型共享的问题,也存在性能的问题(拷贝对象),另外,这种方式很麻烦。
对于道格拉斯▪克罗克福德提出的两种继承方式,参见 《javascript 高级程序设计 第三版》。
本人纯属菜鸟,如果有什么不对的地方,还请指正,原创文章,转载请注明地址。QQ:1261870167