许多OO语言都支持两中继承方式:接口继承和实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。由于函数没有签名,因此ECMAScript无法实现接口继承,只支持实现继承,而实现继承主要是依靠原型链来实现的。
原型链
其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。简单回顾一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针constructor,而实例都包含一个指向原型对象的内部指针[Prototype],那么假如我们让原型对象等于另一个类型的实例,结果会怎样呢?显然,此时的原型对象将包含一个指向另一个原型对象的内部指针。相应的,另一个原型对象也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么层层递进,就形成了实例与原型的链条,这就是所谓原型链的基本概念
实现原型链有一种基本模式,
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){
this.subproperty = false;
}
//继承了SuperType
SubType.prototype = new SuperType();
SubType.prototype.getValue = function(){
return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue());//true
在上面的代码中我们使SubType继承了SuperType,实现的方式就是创建SuperType的实例,实现的本质是重写原型对象。原来存在于SuperType 的实例中的所有属性和方法,现在也存在于SubType.prototype中。在确定了继承关系后,我们给SubType.protoType添加了一个方法,这样就在继承了SuperType的基础上又添加了一个新方法。
新原型不仅不仅具有作为一个SuperType 的实例所拥有的全部属性和方法,而且其内部还有一个指针,指向SuperType的原型。
instance作为SubType的实例,它指向SubType的原型,SubType的原型又指向SuperType的原型。
注意,此时方法getSuperValue属于原型方法,仍然存在于SubType.prototype中,但是属性property则位于SubType.prototype中,这是因为这是一个实例属性,而既然SubType.prototype是SuperType.prototype的实例,那么它的实例属性自然应该位于SubType.prototype中了。
此外要注意,instance.constructor现在指向的SuperType,这是因为原来的SubType.prototype中的constructoe被重写了的缘故。
在有原型链的基础上,原型搜索机制变成了这样:1.搜索实例 2.搜索SubType.prototype 3.搜索SuperType.prototype
在找不到属性和方法的情况下,搜素过程总要一环一环的前行到原型链末端才会停下来。
1.默认的原型
所有函数的默认原型都是Object的实例。因此默认原型都会包含一个内部指针,指向Object.prototype。这也正是所有自定义类型都会继承toString()、valueOf()等默认方法的根本原因。所以一个完整的原型链还应该包含Object.prototype。当调用insatnce.toString()时,实际上是调用Object.prototype里面的方法
2.确定原型与实例之间的关系
第一种方式:使用instanceof操作符
只要用这个操作符来检测实例与原型链中出现过的构造函数,结果就会返回true。
alert(instance instanceof Object); //true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType); //true
由于原型链的关系,我们可以说instance是Object、SuperType、SubType任何一个类型的实例
第二种方式是使用isPrototypeOf()方法,只要在原型链出现过的原型,都可以说是该原型链所派生的实例的原型,因此
alert(Object.prototype.isPrototypeOf(instance)); //true
alert(Super.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.prototype.getSuperType = function(){return false;};
var instance = new SubType();
alert(instance.getSuperValue()); //false
重写超类型中的方法将会屏蔽原来的那个方法。换句话说,当通过SubType的实例调用getSuperValue()时,还会继续调用原来的那个方法。这里要格外注意的是,必须在使用SuperType的实例替换原型之后,再定义这两个方法。
另外,不能使用对象字面量创建原型方法。因为这样会重写原型链
例如:
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;},
someOtherMethod:function(){return false;}
};
var instance = new SubType();
alert(instance.getSuperValue()); //error!
原型链的问题:(1)最主要的问题来自于包含引用类型的值,包含引用类型的原型属性会被所有实例共享,这也是为什么要在构造函数中,而不是在原型对象中定义属性的原因。在通过原型来实现继承时,原型实际上会变成另一个类型的实例。于是,原先的实例属性实际上会变成现在的原型属性。
(2)原型链的第二个问题:在创建子类型的实例时,不能向超类型的构造函数中传递参数。实际上,应该说是没有办法在不影响所有对象实例的情况下给超类型的构造函数传递参数。