许多OO语言都支持两种继承方式:接口继承和实现继承。接口继承只继承方法签名,实现继承则继承实际的方法。由于函数没有签名,在ECMAScript中无法实现接口继承。ECMAScript只支持实现继承,而且其实现继承主要是依靠原型链来实现的。实现的本质是重写原型对象,代之以一个新类型的实例。
1.原型链
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; }; var instance = new SubType(); alert(instance.getSuperValue()); //true
我们没有使用SubType默认提供的原型,而是给它换了一个新原型,这个新原型就是SuperType的实例。于是,新原型不仅具有作为一个SuperType的实例所拥有的全部属性和方法,而且其内部还有一个指针,指向了SupperType的原型。最终:instance指向SubType的原型,SubType的原型又指向SuperType的原型。getSuperValue方法仍然存在于SuperType.prototype中,但property则位于SubType.prototype中。这是因为property是一个实例属性,而getSuperValue则是一个原型方法。
另外,在通过原型链实现继承时,不能使用对象字面量创建原型方法,因为这样做就会重写原型链。(因为直接赋在了prototype上,而不是赋在了添加到prototype的某个方法上)。
问题:就像创建对象时遇到的问题一样,包含引用类型值的原型属性会被所有实例所共享,而这也正是为什么要在构造函数中,而不是在原型对象中定义属性的原因。现在,在通过原型来实现继承时,原型实际上会变成另一个类型的实例,于是,原先的实例属性也就变成了现在的原型属性了。
function SuperType(){ this.colors=["red","blue","green"]; } function SubType(){ } //继承了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.借用构造函数(伪造对象/经典继承)
基本思想:在子类型构造函数的内部调用超类型构造函数。函数只不过是在特定环境中执行代码的对象,因此通过使用apply()和call()方法也可以在(将来)新创建的对象上执行构造函数。
function SuperType(){ this.colors=["red","blue","green"]; } function SubType(){ //继承了SuperType SuperType.call(this); //“借调”了超类型的构造函数 } 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
我们实际上是在(未来将要)新创建的SubType实例的环境下调用了SuperType构造函数,这样一来,就会在新SubType对象上执行SubType函数中定义的所有对象初始化代码。结果,SubType的每个实例就都会具有自己的colors属性的副本了。
问题:方法都在构造函数中定义,函数复用无从谈起。而且,在超类型的原型中定义的方法对子类型不可见。
3.组合继承(伪经典继承)
思路:使用原型链实现对原型属性和方法的继承,通过借用构造函数实现对实例属性的继承。
function SuperType(name){ this.name=name; this.colors=["red","blue","green"]; } 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("black"); alert(instance1.colors); instance1.sayName(); instance1.sayAge(); var instance2 = new SubType("Greg",27); alert(instance2.colors); instance2.sayName(); instance2.sayAge();