1. 原型链
原型链是js中实现继承的主要方法,其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。
实现原型链有一种基本模式,其代码大致如下:
SubType 继承了 SuperType,而继承是通过创建 SuperType 的实例,并将该实例赋给 SubType.prototype 实现的。实现的本质是重写原型对象,代之以一个新类型的实例。在下面例子中,我们没有使用 SubType 默认提供的原型,而是给它换了一个新原型;这个新原型就是 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; }; var instance = new SubType(); alert(instance.getSuperValue()); //true
注意:instance.constructor 现在指向的是 SuperType,这是因为原来 SubType.prototype 中的 constructor 被重写了的缘故。实际上,不是 SubType 的原型的 constructor 属性被重写了,而是 SubType 的原型指向了另一个对象—— SuperType 的原型,而这个原型对象的 constructor 属性指向的是 SuperType。
由于原型链的关系,我们可以说 instance 是 Object、SuperType 或 SubType 中任何一个类型的实例。
原型链继承的问题:1)包含引用类型值的原型。包含引用类型值的原型属性会被所有实例共享;(如下例)2):在创建子类型的实例时,不能向超类型的构造函数中传递参数。实践中很少会单独使用原型链
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"
代码中加粗的那一行代码“借调”了超类型的构造函数。通过使用 call()方法(或 apply()方法 也可以),我们实际上是在(未来将要)新创建的 SubType 实例的环境下调用了 SuperType 构造函数。 这样一来,就会在新 SubType 对象上执行 SuperType()函数中定义的所有对象初始化代码。结果, SubType 的每个实例就都会具有自己的 colors 属性的副本了。
优点:可以在子类型构造函数中向超类型构造函 数传递参数。
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
以上代码中的 SuperType 只接受一个参数 name,该参数会直接赋给一个属性。在 SubType 构造 函数内部调用 SuperType 构造函数时,实际上是为 SubType 的实例设置了 name 属性。
为了确保 SuperType 构造函数不会重写子类型的属性,可以在调用超类型构造函数后,再添加应该在子类型中 定义的属性。
缺点:如果仅仅是借用构造函数,那么也将无法避免构造函数模式存在的问题——方法都在构造函数中定 义,因此函数复用就无从谈起了。而且,在超类型的原型中定义的方法,对子类型而言也是不可见的,结 果所有类型都只能使用构造函数模式。
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); //第二次调用 SuperType() this.age = age; } //继承方法 SubType.prototype = new SuperType(); //第一次调用 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); //"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
组合继承是 JavaScript 常用的继承模式。
组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为 JavaScript 中常用的继 承模式。而且,instanceof 和 isPrototypeOf()也能够用于识别基于组合继承创建的对象。
缺点:无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是 在子类型构造函数内部。
4. 原型式继承
道格拉斯·克罗克福德在 2006年写了一篇文章,题为 Prototypal Inheritance in JavaScript (JavaScript 中的原型式继承)。
这种方法并没有使用严格意义上的构造函数。而是借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。
例:在 object()函数内部,先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。
function object(o){ function F(){} F.prototype = o; return new F(); }
从本质上讲,object()对传入其中的对象执行了一次浅复制。
var person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"] }; var anotherPerson = object(person); anotherPerson.name = "Greg"; anotherPerson.friends.push("Rob"); var yetAnotherPerson = object(person); yetAnotherPerson.name = "Linda"; yetAnotherPerson.friends.push("Barbie"); alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
克罗克福德主张的这种原型式继承,要求你必须有一个对象可以作为另一个对象的基础。如果有这么 一个对象的话,可以把它传递给 object()函数,然后再根据具体需求对得到的对象加以修改即可。
ECMAScript 5通过新增 Object.create()方法规范化了原型式继承。这个方法接收两个参数:一 个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。例:
var person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"] }; var anotherPerson = Object.create(person, { name: { value: "Greg" } }); alert(anotherPerson.name); //"Greg"
应用场景:在没有必要兴师动众地创建构造函数,而只想让一个对象与另一个对象保持类似的情况下。
缺点:包含引用类型值的属性始终都会共享相应的值。
5. 寄生式继承
寄生式(parasitic)继承是与原型式继承紧密相关的一种思路,并且同样也是由克罗克福德推而广之的。
寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该 函数在内部以某种方式来增强对象,后再像真地是它做了所有工作一样返回对象。
注意:示 范继承模式时使用的 object()函数不是必需的;任何能够返回新对象的函数都适用于此模式。
function createAnother(original){ var clone = object(original); //通过调用函数创建一个新对象 clone.sayHi = function(){ //以某种方式来增强这个对象 alert("hi"); }; return clone; //返回这个对象 }
在这个例子中,createAnother()函数接收了一个参数,也就是将要作为新对象基础的对象。然 后,把这个对象(original)传递给 object()函数,将返回的结果赋值给 clone。再为 clone 对象 添加一个新方法 sayHi(),后返回 clone 对象。
createAnother()函数使用:
var person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"] }; var anotherPerson = createAnother(person); anotherPerson.sayHi(); //"hi"
这个例子中的代码基于 person 返回了一个新对象——anotherPerson。新对象不仅具有 person 的所有属性和方法,而且还有自己的 sayHi()方法。
应用场景:主要考虑对象而不是自定义类型和构造函数的情况。
缺点:使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率;这一 点与构造函数模式类似。
6. 寄生组合式继承
寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。
基本思路是:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型 原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。
寄生组合式继承的基本模式如下所示。
function inheritPrototype(subType, superType){ var prototype = object(superType.prototype); //创建对象 prototype.constructor = subType; //增强对象 subType.prototype = prototype; //指定对象 }
这个示例中的 inheritPrototype()函数实现了寄生组合式继承的简单形式。这个函数接收两 个参数:子类型构造函数和超类型构造函数。在函数内部,第一步是创建超类型原型的一个副本。第二 步是为创建的副本添加 constructor 属性,从而弥补因重写原型而失去的默认的 constructor 属性。 后一步,将新创建的对象(即副本)赋值给子类型的原型。
改写前面例子:
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); };
这个例子的高效率体现在它只调用了一次 SuperType 构造函数,并且因此避免了在 SubType. prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用 instanceof 和 isPrototypeOf()。开发人员普遍认为寄生组合式继承是引用类型理想的继承范式。
javascript高级程序设计-继承-总结