javascript 继承

继承通过原型链来实现,我们知道,构造函数的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

原文地址:https://www.cnblogs.com/Moersing/p/4458110.html