JavaScript中的继承
JavaScript中的继承
一、原型链
ECMAScript中描述了原型链的概念,并将原型链作为实现继承的主要方法。
其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。
1 function SuperType(){ 2 this.property = true; 3 } 4 5 SuperType.prototype.getSuperValue = function() { 6 return this.property; 7 }; 8 9 function SubType() { 10 this.subproperty = false; 11 } 12 13 //继承了 SuperType 14 SubType.prototype = new SuperType(); 15 16 SubType.prototype.getSubValue = function(){ 17 return this.subproperty; 18 }; 19 20 var instance = new SubType(); 21 alert(instance.getSuperValue()); //true 22 23 alert(instance instanceof Object); //true 24 alert(instance instanceof SuperType); //true 25 alert(instance instanceof SubType); //true 26 27 alert(Object.prototype.isPrototypeOf(instance)); //true 28 alert(SuperType.prototype.isPrototypeOf(instance)); //true 29 alert(SubType.prototype.isPrototypeOf(instance)); //true
二、借用构造函数
在解决原型中包含引用类型值所带来问题的过程中,开发人员开始使用一种叫做借用构造函数(contructor stealing)的技术(也称伪造对象或者经典继承)。这种技术的基本思想是:在子类型构造函数的内部调用超类型构造函数。
别忘了,函数只不过是在特定环境中执行代码的对象,因此通过使用apply()和call()方法也可以在新创建的对象上执行构造函数。
function SuperType(){ this.colors = ["red", "blue", "green"]; } function SubType(){ SuperType.call(this); } var instance1 = new SubType(); instance1.colors.push("black"); alert(instance1.colors); //"red, blue, green, black" var isntance2 = new SubType(); alert(instance2.colors); //"red, blue, green"
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
2.借用构造函数的问题
如果仅仅是借用构造函数,那么也将无法避免构造函数模式存在的一个问题——方法都在构造函数中定义,因此函数复用就无从谈起了。而且,在超类型的原型中定义的方法,对子类而言也是不可见的,结果所有类型都只能使用构造函数模式。因此,借用构造函数的计数很少单独使用。
三、组合继承
组合继承(combination inheritance),有时候也叫做伪经典继承,指的是将原型链和借用构造函数的计数组合到一起,从而发挥二者之长的一种继承模式。
其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数实现对实例属性的继承。
1 function SuperType(name){ 2 this.name = name; 3 this.colors = ["red", "blue", "green"]; 4 } 5 6 SuperType.prototype.sayName = function(){ 7 alert(this.name); 8 }; 9 10 funciton SubType(name, age){ 11 //继承属性 12 SuperType.call(this, name); 13 14 this.age = age; 15 } 16 17 //继承方法 18 SubType.prototype = new SuperType(); 19 20 SubType.prototpye.sayAge = function(){ 21 alert(this.age); 22 }; 23 24 var instance1 = new SubType("Nicholas", 29); 25 instance1.colors.push("black"); 26 alert(instance1.colors); //"red, blue, green, black" 27 28 instance1.sayName(); //"Nicholas" 29 instance1.sayAge(); //29 30 31 32 var instance2 = new SubType("Greg", 27); 33 alert(instance2.colors); //"red, blue, green" 34 instance2.sayName(); //"Greg" 35 instance2.sayAge(); //27
四、寄生式继承
寄生式(parasitic)继承是与原型继承紧密相关的一种思路。寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真的是它做了所有工作一样返回对象。
function createAnother(original){ var clone = object(orignial); //通过调用函数创建一个新对象 clone.sayHi = function(){ //以某种方式来增强这个对象 alert("hi"); }; return clone; //返回这个对象 }
object() 函数不是必须的,任何能够返回想对象的函数都适于此模式。
使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率;这一点与构造函数模式类似。
五、寄生组合式继承(有效的)
组合继承最大的问题就是无论在什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。
子类型最终会包含超累型对象的全部实力属性,但我们不得不在调用子类型构造函数时重写这些属性。
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); //第二次调用superType() this.age = age; } SubType.prototype = new SuperType(); //第一次调用SuperType() SubType.prototype.sayAge = function(){ alert(this.age); };
两次调用SuperType()的结果就是 name和age属性有两组,一组在SubType原型中,一组在SubType属性中。通过使用寄生组合式继承,可以解决这个问题。
所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。其背后的基本思路是:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。
(问题:这样的话,超类型中除原型意外的方法如何得到继承?)
1 function inheritPrototype(subType, superType){ 2 var prototype = object(superType.prototype); //创建对象 3 prototype.constructor = subType; //增强对象 4 subType.prototype = prototype; //指定对象 5 } 6 7 ========================================================================= 8 9 使用这种形式改写之前的继承: 10 11 function SuperType(name){ 12 this.name = name; 13 this.colors = ["red", "blue", "green"]; 14 } 15 16 SuperType.prototype.sayName = function(){ 17 alert(this.name); 18 }; 19 20 function SubType(name, age){ 21 SuperType.call(this, name); 22 23 this.age = age; 24 } 25 26 inheritPrototype(SubType, SuperType); 27 28 SubType.prototype.sayAge = function(){ 29 alert(this.age); 30 };