关闭

Javascript继承

标签: javascript继承
183人阅读 评论(0) 收藏 举报
分类:

一、原型链继承方式

1、原形链继承做法
示例:

function SuperType(){
          this.property=true;
}
Super.prototype.getSuperValue=function(){
          return this.property;
}

function SubType(){
          this.subProperty=false;
}
SubType.prototype=new SuperType();
SubType.prototype.getSubValue=function(){
          return this.subProperty;
}
var s=new SubType();
console.log(s. getSuperValue());

2、优势
原形链的优势来自于共享,如getSuperValue,无论new多少个SubType,都只共享同一个getSuperValue


3、缺点
只能实现单一继承。
例如SubType.prototype=new SuperType1();如果还想让SubType.prototype再继承SuperType2,那么SubType.prototype=new SuperType2 ();结果是SubType将不再继承SuperType1而只继承SuperType2

没有很好地解决属性共享问题
这是原形链最大的问题。例如:

function SuperType(){
           this.names={“Leo”,”Linda”};
}
SuperType.prototype.getName=function(){return this.name};
function SubType(){}
SubType.prototype=new SuperType();//此时SubType.prototype会具有name属性,
//这会引起属性共享问题
var s1=new SubType();
var s2=new SubType();
s2.names.push(“cici”);//此时s1的names也改变了

没有很好地解决类型识别问题

function SuperType(){}
function SubType(){}
SubType.prototype=new SuperType();
var instance=new SubType(); 

此时instance的constructor指向的是SuperType,而不是SubType。


4、点评
原形链继承,成也共享败也共享


二、借用构造函数继承

通过call跟apply借用超类的构造函数,原理跟使用构造函数创建对象一样。

1、借用构造函数继承做法
示例:

function SuperType(name){
           this.name=name;
           this.sayName=function(){alert(name);}
}
SuperType.prototype.sayHi=function(){alert(“Hi”);}
function SubType(){
           SuperType.call(this,”Leo”);
           this.age=18;
}

var obj=new SubType();//new调用SubType构造函数,于是SubType函数里面的this
//指向obj。


2、优势
借用构造函数解决了原形链继承的共享问题
借用构造函数的问题:


3、缺点
属性无法共享
如sayName是一个函数,理应所有对象共享,而不是没创建一个对象,就要给这个对象添加一次sayName方法。

超类原型中的属性对子类不可见
例如上面在SuperType的原型中定义了sayHi方法,但是子类是不会有sayHi方法的。


4、点评
借用构造函数解决了原形链的共享问题,但是也导致该共享的属性如:函数,无法共享。所以借用构造函数继承解决了共享问题,又败在共享


三、组合继承(也叫伪经典继承)

1、组合继承做法
示例:

function SuperType(){
this.color=["red","blue"];
}
SuperType.prototype.getColor=function(){console.log(this.color);};

function SubType(){
          SuperType.call(this);
}
SubType.prototype=new SuperType();

var obj1=new SubType();
var obj2=new SubType();
obj1.color.push("green");
console.log(obj1.color);//输出red,blue,green
console.log(obj2.color);//输出red,blue
console.log(SubType.prototype.color);//输出red,blue

2、优势
组合继承融合了原形链和借用构造函数之所长,让该共享的属性共享,不该共享的属性不共享。可以说是一种比较理想的实现继承的手段。


3、缺点
虽然组合继承已经是很好的一种继承方式了,但是组合继承还有个小小的遗憾,就是使子类的原型跟子类的实例各自存放一份属性。如上面示例的SubType的原型就有一个color,而obj1和obj2实例本身也有color,于是会有这样的结果,如果delete obj2.color,然后console.log(obj2.color);仍然会输出red,blue,delete后的color来自于SubType原型


4、点评
组合继承集原形链继承及借用构造函数继承之所长,解决了共享问题,让该共享的属性共享,不该共享的属性不共享。虽然还有个小缺点,但是已经算是很理想的继承方式了


四、原型式继承与寄生式继承

所谓原型式继承就是以一个对象A作为另一个对象B的原型,并创建B。所谓寄生式继承,其实就是对原型式继承做了一层简单的封装。

1、原型式与寄生式继承做法
示例:

function extend(obj){
          function F(){}
          F.prototype=obj;
          return new F();
}
var Person = {
          name : '名称',  //名称
          friends:["Simen","Van"],//引用此属性需要注意原型共享问题
          getName : function(){
              return this.name;
          },
};
function createAuthor(name,book){
          var author=extend(Person);//原型式继承Person
          author.name=name;
          author.book=book; //扩展Person
          return author;
}
function createCoder(name,language){
          var coder=extend(Person); //原型式继承Person
          coder.name=name;
          coder.language=language;//扩展Person
          return coder;
}
var author=createAuthor("莎士比亚", "哈姆雷特");//整个createAuthor的过程就是寄生
//式继承
console.log("作者:"+author.getName()+" 代表作:"+author.book);//getName来自
//Person

var coder=createCoder("Leo", "C++");//整个createCoder的过程就是寄生式继承
console.log("码农:"+coder.getName()+"  语言:"+coder.language); //getName函数来
//自Person

console.log(author.friends);//注意friend是author的原型Person的属性,会有原型共//享问题
console.log(coder.friends);
author.friends.push("Lemon");
console.log(author.friends);
console.log(coder.friends);

2、优势
可以直接利用现有对象进行扩展,而不必要去兴师动众地声明类


3、缺点
作为原型的对象,最好只有方法,没有属性。如果有属性,最好不要使用属性,否则需要注意原型属性的共享问题。如果共享正好就是你想要的,那就不会有什么问题。
上面的示例代码中,author和coder都以Person对象为原型实现了继承,并各自进行了扩展,author增加了book属性,coder则增加了language属性。由于author和coder的原型都是Person,Person有getName,所以author和coder都可以调用getName。也就是说getName是放在Person中作为原型属性被author和coder共享的,这很好,将函数作为原型属性共享通常是好事。但是注意Person有friends属性,这可能会引起原型共享的问题。所以结论很明显,被继承的对象,这里是Person,最好是只有方法,如果有属性,例如friends,如果需要使用friends的话,需要注意原型共享的问题。


五、寄生组合式继承

前面说过,组合继承有个缺点,就是组合继承调用了两次构造函数,即在子类内部借用构造函数,以及子类的原型以new形式调用超类构造函数,导致子类存有两份超类的实例属性。

1、组合继承的缺点:

function SuperType(){
           this.name=”Leo”;
}
SuperType.prototype.sayName=function(){
           alert(this.name);
}
function SubType(){
           SuperType.call(this);//继承实例属性
}
SubType.prototype=new SuperType();//这里超类的实例与原型属性都继承了,需要注//意的是,上面已经继承过实例属性

2、组合继承的改进方案:寄生组合式继承

function createObjectWithPrototype(obj){
           function F(){}
           F.prototype=obj;
           return new F();
}
function inheritPrototype(SubType,SuperType){
var prototype=createObjectWithPrototype(SuperType.prototype);
prototype.constructor=SubType;
SubType.prototype=prototype;
}

function SuperType(){
this.name="Leo";
}
SuperType.prototype.sayName=function(){console.log(this.name);};


function SubType(){
SuperType.call(this);
this.age=18;
}

inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge=function(){
console.log(this.age);
};
var s=new SubType();
s.sayAge();
s.sayName();

3、对寄生组合式继承封装

function createObjectWithPrototype(obj){
function F(){}
F.prototype=obj;
return new F();
}
function inheritPrototype(SubType,SuperType){
var prototype=createObjectWithPrototype(SuperType.prototype);
prototype.constructor=SubType;
SubType.prototype=prototype;
}
/**
*若超类有参数,需要指定第三个参数。影响性能主要因素有超类的参
*数个数,超类参数越多性能越低。如果要传参,最好超类与子类都采
*用参数对象进行传参。另一个影响性能的因素就是在调用extend之
*对子类的原型进行了扩展,扩展越多,性能越低。最好是在extend之
*后再对子类原型进行扩展
*/
function extend(SubType,SuperType,subTypeArgsNum){
if(arguments.length<2) 
throw new Error("参数错误,至少要两个参数");
if(typeof SubType!="function" || typeof SuperType!="function")
throw new Error("参数类型错误,第一个参数需要传入function类型");
if(typeof subTypeArgsNum!="number" && subTypeArgsNum!=null)
throw new Error("参数类型错误,第三个参数需要传入数值类型或者为空");

function F(SubTypeArgs){//构造函数继承
SubType.apply(this,arguments);
if(subTypeArgsNum!=null){
var argLength=arguments.length;
var superArgs=[];
for(var i=subTypeArgsNum;i<argLength;i++){
superArgs.push(arguments[i]);
}
SuperType.apply(this,superArgs);
}
else SuperType.apply(this);
};
inheritPrototype(F, SuperType);//原型继承
var subTypePrototype=SubType.prototype;
var prototype=F.prototype;
for(property in subTypePrototype){
               prototype[property]=subTypePrototype[property];
}
prototype.constructor=SubType;
return F;
}       

function SuperType(name){
this.name=name;
}
SuperType.prototype.sayName=function(){console.log(this.name);};

function SubType(ageObj){
this.age=ageObj.age;
}
SubType.prototype.sayAge=function(){
console.log(this.age);
};
SubType=extend(SubType,SuperType,1);        
var s=new SubType({age:18},"Leo");
s.sayAge();
s.sayName();

将继承过程封装起来后,实现了子类与超类继承的解耦,提高了代码的简洁性。不
过这是以较大的性能损耗作为代价的。还有待改进


4、优势
寄生组合继承,在组合继承的基础上对其进行改进,解决了组合继承的缺点。
可以说是一种近乎完美的继承方案


5、缺点
寄生组合继承的缺点就是比组合继承略微复杂点,不过这不是什么问题。


6、组合、组合寄生与组合寄生封装后的性能比较
组合继承,组合寄生继承与组合寄生封装后的继承性能比较:组合继承与组
合寄生继承性能相当。但是函数封装后实现继承所需时间是则是组合继承或
组合寄生继承所花时间的3~10倍,性能损失很大。但是封装函数实现继承比
较简单,而且由于统一使用继承函数,代码更加简洁可维护,可根据实际情
况选用继承方式

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:9239次
    • 积分:235
    • 等级:
    • 排名:千里之外
    • 原创:14篇
    • 转载:5篇
    • 译文:0篇
    • 评论:0条