这一部分主要讲讲有关继承的问题。
6.3 继承
许多面向对象语言比如java都支持两种继承方式:接口继承(只继承方法签名)和实现继承(继承实际的方法);由于函数没有签名,在ECMAScript中只能支持实现继承。实现继承主要依靠原型链。
6.3.1 原型链
基本思想:利用原型让一个引用类型继承另一个引用类型的属性和方法。
回顾一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。
所以,试想一下,我们让原型对象等于另一个类型的实例,结果原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针;看图和例子吧
function SuperType(){ this.property = true; } SuperType.prototy.getSuperValue = function(){ return this.property; }; function SubType(){ this.subproperty = false; } //继承了SuperType SubType.prototype = new SuperType();//SubType.prototype 是SuperType的实例,其内部指针自然指向SuperType的原型 SubType.prototype.getSubvalue = function(){ return this.subproperty; }; var instance = new SubType(); alert(instance.getSuperValue);
分别定义了两个类型,都有各自方法和属性;继承是通过创建SuperType的实例,并将该实例赋给SubType.prototype实现的。本质是重写原型对象,代之以一个新类型的实例。(注意是先重写后创建新实例var instance = new SubType())。使得原来存在于Supertype的实例中所有的属性和方法,现在也存在于SubType.prototype中。继承了属性和方法,也可以自己添加方法。
注意,红框和绿框所标注的,其SubType.prototype内部还有一个指针,指向SuperType的原型;property位于SubType.prototype,因为SubType.prototype是SuperType的一个实例属性。
此外,要注意instance.constructor(注疑问,原型对象才有constructor难道不是Subtype.prototype.constructor)现在指向的是SuperType。因为SubType的原型指向了另一个对象—>SuperType的原型,而这个原型对象的constructor属性指向的是SuperType,记住一切来自引用。
所有引用类型默认都继承了Object,也是通过原型链。所以有当调用instance.toString()时,实际上调用的是保存在Object.prototype中的那个方法。
如何确定原型和实例的关系,有两种。1)采用instanceof 2)采用isPrototypeof()方法;如下例子
alert(Object.prototype.isPrototypeof(instance));//true
alert(SuperType.prototype.isPrototypeof(instance));//true
alert(SubType.prototype.isPrototypeof(instance));//true
谨慎地定义方法
在子类有时候需要覆盖超类型中的某一个方法或需要添加超类型中不存在的某个方法。但不管怎样,给原型添加方法的代码一定要放在替换原型的语句之后。(注:因为如果在之前的话,被洗刷掉了,原型引用的地方是别处父级Supertype.prototype).还有一点注意,在通过原型链实现继承时,不能使用对象字面量创建原型方法。因为会重写原型链,切断联系。
function SuperType(){ this.property = true;//父类属性 } SuperType.prototype.getSuperValue = function(){//父类原型链的方法 return this.property; }; function SubType(){ this.subproperty = false; } //inherit from SuperType//创建父类实例给子类的原型链(XXXX) SubType.prototype = new SuperType(); //new method添加新方法 SubType.prototype.getSubValue = function (){ return this.subproperty; }; //override existing method覆盖超类型中的方法 SubType.prototype.getSuperValue = function (){ return false; }; var instance = new SubType(); alert(instance.getSuperValue()); //false
原型链的问题
问题1:前面街上包含引用类型值的原型属性会被所有实例共享。在通过原型来实现继承是,原型实际上会编程另一个类型的实例。
function SuperType(){ this.colors = ["red", "blue", "green"]; } function SubType(){ } //inherit from SuperType SubType.prototype = new SuperType(); var instance1 = new SubType(); instance1.colors.push("black"); alert(instance1.colors); //"red,blue,green,black" var instance2 = new SubType(); alert(instance2.colors); //"red,blue,green,black"
问题2:在创建子类型的实例时,不能像超类型的构造函数中传递参数。(实际上,应该说是没有办法在不影响所有对象实例的情况下,给超类型的构造函数床底参数。)所以实践中很少单独使用原型链。
下一部分介绍,实践中的继承技术。