1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | function SuperType(){ this .colors=[ "red" , "blue" , "green" ]; } function SubType(){ //继承了SuperType SuperType.call( this ); } var instance= new SubType(); instance.colors.push( "black" ); //"red","blue","green","black" alert(instance.colors); var instance2= new SubType(); alert(instance2.colors); //"red","blue","green" |
将SuperType构造函数放到SubType的执行环境中,这样一来,就会在新SubType对象上执行SuperType()函数中定义的所有对象初始化代码,结果,SubType的每一个实例都有一份具有自己colors属性的副本。
(1)传递参数
借用构造函数可以在子类型构造函数中向父类型构造函数传递参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | function SuperType(name){ this .name=name; } function SubType(){ //继承了SuperType,同时传递参数 SuperType.call( this , "zxj" ); //实例属性 this .age=29; } var instance= new SubType(); alert(instance.name); //zxj alert(instance.age); //29 |
(2)借用构造函数的问题
如果仅仅使用借用构造函数,那么将无法避免构造函数模式存在的问题——方法都在构造函数中定义,函数的复用也无从谈起。所以借用构造函数也很少单独使用。
2.组合继承
组合继承(伪经典继承),指的是将原型链和借用构造函数的技术组合在一块。其思路是使用原型链对原型属性和方法的继承,而通过借用构造函数来实现对实例对象的继承。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 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 instance= new SubType( "zxj" ,29); instance.colors.push( "black" ); alert(instance.colors); //"red","blue","green","black" instance.sayName(); //zxj instance.sayAge(); //29 var instance2= new SubType( "Greg" ,28); alert(instance.colors); //"red","blue","green","black" instance2.sayName(); //Greg instance2.sayAge(); //28 |
组合继承避免了原型链和借用构造函数的缺陷,融合了他们的有点,成为JavaScript中追尾常用的继承模式。而且,instanceof和isPrototypeof()也能够用于识别组合继承创建的对象。
3.原型式继承
借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。
1 2 3 4 5 | function object(o){ function F(){}; F.prototype=o; return new F(); } |
在object()函数内部,先创建一个临时性的构造函数,然后将传入的对象作为该构造函数的原型,最后返回这个临时类型的一个新实例。本质上讲,object()对传入其中的对象进行了一次浅复制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | function object(o){ function F(){}; F.prototype=o; return new F(); } var person={ name: "zxj" , friends:[ "Shelby" , "Court" , "Van" ] }; var anotherPerson=object(person); antherPerson.name= "Greg" ; anotherPerson.friends.push( "Rob" ); var yetAnotherPerson=object(person); yetAnotherPerson.name= "Linda" ; yetAnotherPerson.friends.push( "Brabie" ); alert(person.friends); //"Shelby,Court,Van,Rob,Brabie" |
但包含引用类型值的属性始终后悔共享相应的值,这和原型模式一样的。
4.寄生式继承
寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式类增强对象,最后再像真的是它做了所有工作一眼放回对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | function creteAnother(original){ var clone=object(original); //通过调用函数创建一个新对象 clone.sayHi= function (){ //以某个方式来增强这个对象 alert( "hi" ); } return clone; //返回这个对象 } var person={ name: "zxj" , friends:[ "Shelby" , "Court" , "Van" ] }; var anotherPerson=createAnother(person); anotherPerson.sayHi(); //"hi" |
使用寄生式来为对象添加函数,会由于不能做到函数服用而降低效率。
5.寄生组合式继承
组合继承是JavaScript中最常用的继承模式,但它无论在什么情况下,都会调用两次父类型构造函数:一次在创建子类型原型时,一种在子类型构造函数内部。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | function SuperType(name){ this.name=name; this.colors=[ "red" , "blue" , "green" ]; } 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); } |
第一次调用父类型时,SubType.prototype会得到两个属性:name和colors;他们都是SuperType的实例属性,只不过位于SubType的原型中。带调用SubType构造函数时,有一次调用了SuperType构造函数,这一次是在新对象上创建实例属性name和colors,于是这两个属性就屏蔽掉了原型中的两个同名属性,调用过程如图所示。
这样就有两组name和colors属性:一组在实例上,一组在SubType的原型中,这就是调用两次SuperType构造函数的结果。
当然,我们可以通过寄生组合是继承来解决这问题,所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链来继承方法。思路:不必为了指定子类型的原型而调用父类型的构造函数,我们只需父类型原型的一个副本。本质上,就是使用寄生式继承来继承父类型的原型,然后将结果指定给子类型的原型,寄生组合式继承的基本模式如下:
1 2 3 4 5 | function inheritPrototype(subType,superType){ var prototype=object(superType.prototype); //创建对象 prototype.constructor=subTyoe; //增强对象 subType.prototype=prototype; //指定对象 } |
示例中inheritPrototype()函数实现了寄生组合式继承的最简单形式。这个函数接收两个参数:子类型构造函数和父类型构造函数。在函数内部:第一步是创建父类型原型的一个副本。第二步是为创建的副本调价constructor属性,从而弥补因重写原型而失去默认的constrcutor属性。最后一步,将新创建的对象(即副本)赋值给子类型的原型。这样,我就可以调用inheritPrototype()函数的语句,去替换前面例子中为子类型原型赋值的语句。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | function object(o){ function F(){} F.prototype = o; return new F(); } function inheritPrototype(subType, superType){ var prototype = object(superType.prototype); //create object prototype.constructor = subType; //augment object subType.prototype = prototype; //assign 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); this .age = age; } inheritPrototype(SubType, SuperType); SubType.prototype.sayAge = function (){ alert( this .age); }; var instance1 = new SubType( "Nicholas" , 29); instance1.colors.push( "black" ); alert(instance1.colors); //"red,blue,green,black" instance1.sayName(); //"Nicholas"; instance1.sayAge(); //29 var instance2 = new SubType( "Greg" , 27); alert(instance2.colors); //"red,blue,green" instance2.sayName(); //"Greg"; instance2.sayAge(); //27 |