继承分为六部分:原型链、借用构造函数、组合继承、原型式继承、寄生式继承、寄生组合式继承。 今天时间有限,先整理下前3个,稍后再给各位粉丝和看客们分享后3个。如有遗漏或失误之处,欢迎各位积极指正。话不多说,进入正题: 很多语言都支持两种继承方式:接口继承和实现继承。接口继承只继承方法签名。实现继承则继承实际方法。由于函数没有签名,在ECMAScript中无法实现接口继承。ECMAScript只支持实现继承,而且其实现继承主要是依靠原型链来实现的。
一.原型链:利用原型让一个引用类型继承另一个引用类型的属性和方法。
使用原型链有一种基本模式,如下:
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
}
function SubType(){
this.subproperty = false;
}
//继承了SuperType()
SubType.prototype = new SuperType();
SubType.prototype.getSuperValue = function(){
return this.subproperty;
}
var instance = new SubType();
alert(instance.getSuperValue());//true
//instance.getSuperValue()经历了三个搜索步骤:
//(1)搜索实例
//(2)搜索SubType.prototype
// (3)搜索SuperType.prototype,最后一步才会找到该方法。
//搜索过程总是要一环一环地前行,到原型链末端才会停下来。
1.默认的原型
所有函数都有默认原型Object实例,默认原型都包含一个内部指针,指向Object.prototype。这也是所有自定义类型都会继承toString()、valueOf()等默认方法的根本原因。 上述案例:SubType继承了SuperType,而SuperType继承了Object。当调用instance.toString()时,实际上调用的是保存在Object.prototype中的那个方法。
2.确定原型和实例的关系
可以通过两种方式确定原型和实例之间的关系:(1)使用instanceof()操作符,用它来检测实例与原型链中出现过的构造函数,结果就会返回true。(2)使用isPrototypeOf()方法,只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型,isPrototypeOf()方法也会返回true。 (1)instanceof()
alert(instance instanceof Object);//true
alert(instance instanceof SuperType);//true
alert(instance instanceof SubType);//true
由于原型链的关系,我们可以说instance是Object、SuperType或SubType中任何一个类型的实例。
(2)isPrototypeOf()
alert(Object.prototype.isPrototypeOf(instance));//true
alert(SuperType.prototype.isPrototypeOf(instance));//true
alert(SubType.prototype.isPrototypeOf(instance));//true
3.谨慎定义方法
子类型有时候需要覆盖超类型中的某个方法,或者需要添加超类型中不存在的某个方法。但无论如何,给原型添加方法的代码一定要放在替换原型的语句之后。
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){
this.subproperty = false;
}
//继承了SuperType
SubType.prototype = new SuperType();
//添加新方法
SubType.prototype.getSubValue = function(){
return this.subproperty;
};
//重写超类型中的方法 通过SubType的实例调用会屏蔽最前面定义的getSuperValue
SubType.prototype.getSuperValue = function(){
return false;
};
var instance = new SubType();
alert(instance.getSuperValue());//false
ps:通过原型链实现继承时,不能使用对象字面量创建原型方法。因为这样会重写原型链。
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){
this.subproperty = false;
}
//继承了SuperType
SuperType.prototype = new SuperType();
//使用字面量添加新方法,会导致上一行代码无效
SubType.prototype = {
getSubValue:function(){
return this.subproperty;
},
someOtherMethod:function(){
return false;
}
};
var instance = new SubType();
alert(instance.getSuperValue());//error
以上代码展示了刚刚把SuperType的实例赋值给原型,紧接着又将原型替换成啦一个对象字面量而导致的问题。由于现在的原型包含了一个Object的实例,而非SuperType的实例,因此我们设想中的原型链已经被切断------SubType和SuperType之间已经没有关系了。
4.原型链的问题
原型链的问题有两个: (1)最主要的来自包含引用类型值的原型。引用类型值的原型属性会被所有实例共享,而这也是为什么要在构造函数中,而不是原型对象中定义属性的原因。
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("yellow");
alert(instance1.colors);//red,pink,green,yellow
var instance2 = new SubType();
alert(instance2.colors);//red,pink,green,yellow
(2)在创建子类型的实例时,不能向超类型的构造函数中传递参数。实际上,应该说是没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。由于原型中包含引用类型值所带来的问题,实践中很少会单独使用原型链。
二.借用构造函数
在解决原型中包含引用类型值所带来问题的过程中,开始使用一种叫做“借用构造函数”coustructor stealing的技术(有是也叫伪造对象或者经典继承)。 基本思想:在子类型构造函数的内部调用超类型构造函数。函数只不过是在特定环境中执行代码的对象,因此使用apply()和call()方法也可以在新创建的对象上执行构造函数。
function SuperType(){
this.colors = ["red","yellow","green"];
}
function SubType(){
//继承了SuperType
SuperType.call(this);//重点“借调”了超类型的构造函数
}
var instance1 = new SubType();
instance1.colors.push("violet");
alert(instance1.colors);//red,yellow,green,violet
var instance2 = new SubType();
alert(instance2.colors);//red,yellow,green
使用call()方法,在新创建的SubType实例的环境下调用了SuperType构造函数。就会在新的SubType对象上执行SuperType()函数中定义的所有对象初始化代码。结果SubType的每个实例都会具有自己的colors属性的副本了。
1.传递参数
相对于原型链而言,借用构造函数有一个很大的优势。可以在子类型构造函数中向超类型的构造函数传递参数。
function SuperType(name){
this.name = name;
}
function SubType(){
//继承了SuperType,同时还传递了参数
SuperType.call(this,"Nicholas");
//实例属性
this.age = 29;
}
var instance = new SubType();
alert(instance.name);//"Nicholas";
alert(instance.age);//29
SuperType只接受了一个参数name,该参数会直接赋给一个属性。在SubType构造函数内部调用SuperType构造函数时,实际上是为SubType的实例设置了name属性。为了确保SuperType构造函数不会重写子类型的属性,可以在调用超类型构造函数后,再添加应该在子类型中定义的属性。
2.借用构造函数的问题。
方法都在构造函数中定义,因此函数复用就无从谈起。而且,在超类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。考虑到这些问题,借用构造函数的技术也很少单独使用。
三.组合继承(combination inheritance)
也称伪经典继承,是指将原型链和借用构造函数的技术组合在一块,发挥二者之长的一种继承模式。思路:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能保证每个实例都有它自己的属性。
function SuperType(name){
this.name = name;
this.colors = ["red","yellow","violet"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name,age){
//继承属性
SuperType.call(this,name);
this.age = age;
}
//继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
};
var instance1 = new SubType("Nicholas",29);
instance1.colors.push("orange");
alert(instance1.colors);//red,yellow,violet,orange
instance1.sayName();//"Nicholas"
instance1.sayAge();//29
var instance2 = new SubType("Greg",27);
alert(instance2.colors);//red,yellow,violet
alert(instance2.sayName());//Greg
alert(insatnce2.sayAge());//27
组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为了JavaScript中最常用的继承模式。instanceof和isPrototypeOf()也能够用于识别基于组合继承创建的对象。
|