一、原型链
见上一篇原型链;
原型链的问题:1.用原型链来实现继承时,最主要的问题来自包含引用类型值的原型。
function SuperType(){
this.colors=['red','green'];
}
function SubType(){}
SubType.prototype=new SuperType();
var instance1=new SubType();
instance1.colors.push('black');
alert(instance1.colors)//red,green,black
var instance2=new SubType();
alert(instance2.colors)//red,green,black
构造函数中定义的属性都是实例属性,原型中定义的属性由所有属性共享。
因为subtype继承了supertype,因此supertype中的属性colors变成了subtype的原型对象中的属性,因此在subtype的实例中,colors变成了共享的属性。
2.在创建子类型的实例时,不能向超类型的构造函数中传递参数。
二、借用构造函数
在子类型构造函数内部调用超类型构造函数。
function SuperType(){
this.colors=['red','green'];
}
function SubType(){
SuperType.call(this);//调用超类型的构造函数
}
var instance1=new SubType();
instance1.colors.push('black');
alert(instance1.colors)//red,green,black
var instance2=new SubType();
alert(instance2.colors)//red,green
相对于原型链,借用构造函数的一个很大的优势是可以在子类型构造函数中向超类型构造函数传递参数。
function SuperType(name){
this.name=name;
}
function SubType(){
SuperType.call(this,'Nic');
this.age=29;//为了确保SuperType构造函数不会重写子类型的属性,可以在调用超类型构造函数之后再添加应该在子类型中定义的属性
}
var instance=new SubType();
alert(instance.name)//'Nic'
alert(instance.age)//29
存在的问题:
无法避免构造函数模式存在的问题,方法都在构造函数中定义,因此函数复用无从谈起。而且,在超类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。
三、组合继承
使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。
function SuperType(name){
this.name=name;
this.colors=['red','green'];
}
SuperType.prototype.sayName=function(){alert(this.name)};
function SubType(name,age){
SuperType.call(this,name);//第二次调用SuperType()
this.age=age;
}
SubType.prototype=new SuperType();//第一次调用SuperType()
SubType.prototype.constructor=SubType;
SubType.prototype.sayAge=function(){alert(this.age)};
var instance1=new SubType('Nic',29);
instance1.colors.push('black');
alert(instance1.colors)//red,green,black
instance1.sayName();//Nic
instance1.sayAge()//29
var instance2=new SubType('Greg',27);
alert(instance2.colors)//red,green
instance2.sayName()//Greg
instance2.sayAge()//27
组合继承是JS最常用的继承模式;不过,它也有自己的不足。组合继承最大的问题是无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。
在第一次调用SuperType构造函数时,SubType.prototype会得到两个属性:name和colors;它们都是SuperType的实例属性,只不过现在位于SubType的原型中。当调用SubType构造函数时,又会调用一次SuperType构造函数,这一次又在新对象上创建了实例属性name和colors。于是,这两个属性就屏蔽了原型中的两个同名属性。
四、寄生组合式继承
寄生组合式继承即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。基本思路:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。基本模式如下
function object(o){
function F(){}
F.prototype=o;
return new F();
}//返回传入对象的原型副本
function inheritPrototype(subType,superType){
var prototype=object(superType.prototype);//prototype是superType原型副本
prototype.constructor=subType;
subType.prototype=prototype;
}
function SuperType(name){
this.name=name;
this.colors=['red','green'];
}
SuperType.prototype.sayName=function(){
alert(this.name);
};
function SubType(name,age){
SuperType.call(this,name);
this.age=aage;
}
inheritPrototype(SubType,SuperType);
SubType.prototype.sayAge=function(0{
alert(this.age);
}
开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。
五、总结
JS主要通过原型链实现继承。原型链的构建是通过将一个类型的实例赋值给另一个构造函数的原型实现的。这样子类型就可以访问超类型的所有属性和方法。原型链的问题是对象实例共享所有继承的属性和方法,因此不适宜单独使用。
构造函数,在子类型构造函数内部调用超类型的构造函数。这样就可以做到每个实例都有自己的属性,同时还能保证只使用构造函数模式来定义类型。
使用最多的是组合继承,这种模式使用原型链继承共享的属性和方法,而通过借用构造函数实现继承实例属性。