初识JavaScript-继承

来源:互联网 发布:淘宝女装店铺设计图 编辑:程序博客网 时间:2024/06/07 18:10

ECMAScript 只支持实现继承,而且其实现继承主要是依靠原型链来实现的。

原型链

原型链作为实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。

function SuperType(){    this.property = true;}SuperType.prototype.getSuperValue = function(){    return this.property;};function SubType(){    this.subproperty = false;}//继承了 SuperTypeSubType.prototype = new SuperType();SubType.prototype.getSubValue = function (){    return this.subproperty;};var instance = new SubType();alert(instance.getSuperValue()); //true

这个例子中的实例以及构造函数和原型之间的关系如下图所示。

图中SubType.prototypeprototype属性为true,是因为property 是一个实例属性,而 getSuperValue()则是一个原型方法。而且 SubType.prototype 现在是 SuperType的实例,那么 property 当然就位于该实例中了。

默认的原型

所有函数的默认原型都是 Object 的实例,因此默认原型都会包含一个内部指针,指向 Object.prototype。这也正是所有自定义类型都会继承toString()valueOf()等默认方法的根本原因。
所以上图还应添加Object对象

确定原型和实例的关系

可以通过两种方式来确定原型和实例之间的关系。第一种方式是使用 instanceof 操作符,只要用这个操作符来测试实例与原型链中出现过的构造函数,结果就会返回 true。

alert(instance instanceof Object); //truealert(instance instanceof SuperType); //truealert(instance instanceof SubType); //true

第二种方式是使用 isPrototypeOf()方法。同样,只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型,因此 isPrototypeOf()方法也会返回 true。

alert(Object.prototype.isPrototypeOf(instance)); //truealert(SuperType.prototype.isPrototypeOf(instance)); //truealert(SubType.prototype.isPrototypeOf(instance)); //true

谨慎定义方法

给原型添加方法的代码一定要放在替换原型的语句之后。

function SuperType(){    this.property = true;}SuperType.prototype.getSuperValue = function(){    return this.property;};function SubType(){    this.subproperty = false;}//继承了 SuperTypeSubType.prototype = new SuperType();//添加新方法SubType.prototype.getSubValue = function (){    return this.subproperty;};//重写超类型中的方法SubType.prototype.getSuperValue = function (){    return false;};var instance = new SubType();alert(instance.getSuperValue()); //falsevar instance1 = new SuperType();alert(insance1.getSuperValue());//true

所以必须在用 SuperType 的实例替换原型之后,再定义这两个方法。

在通过原型链实现继承时,不能使用对象字面量创建原型方法。因为这样做就会重写原型链。

function SuperType(){    this.property = true;}SuperType.prototype.getSuperValue = function(){    return this.property;};function SubType(){    this.subproperty = false;}//继承了 SuperTypeSubType.prototype = new SuperType();//使用字面量添加新方法,会导致上一行代码无效SubType.prototype = {    getSubValue : function (){        return this.subproperty;    },    someOtherMethod : function (){        return false;    }};var instance = new SubType();alert(instance.getSuperValue()); //error!

原型链的问题

  1. 来自包含引用类型值的原型,会造成属性被实例所共享。
  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"

传递参数

借用构造函数有一个很大的优势,即可以在子类型构造函数中向超类型构造函数传递参数。

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

借用构造函数问题

方法都在构造函数中定义,因此函数复用就无从谈起了。而且,在超类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。

组合继承

指的是将原型链和借用构造函数的技术组合到一块,其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承

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构造函数与原型重新连接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(); //29var instance2 = new SubType("Greg", 27);alert(instance2.colors); //"red,blue,green"instance2.sayName(); //"Greg";instance2.sayAge(); //27

将 SuperType 的实例赋值给 SubType 的原型,然后又在该新原型上定义了方法 sayAge()。这样一来,就可以让两个不同的 SubType 实例既分别拥有自己属性——包括 colors 属性,又可以使用相同的方法了。

组合继承模式成为 JavaScript 中最常用的继承模式。而且, instanceof 和 isPrototypeOf()也能够用于识别基于组合继承创建的对象。

原型式继承

基本思想是借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。

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()函数,然后再根据具体需求对得到的对象加以修改即可。上面的例子中,可以作为另一对象的基础是person对象,所以object()函数返回的是将person视为原型的对象。所以可以认为anotherPersonyetAnotherPersonperson对象的两个副本。

ECMAScript 5 通过新增 Object.create()方法规范化了原型式继承。这个方法接收两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。在传入一个参数的情况下,Object.create()与 object()方法的行为相同。
所以上面例子中,就可以写成var anotherPerson = Object.create(person);

Object.create()方法的第二个参数可以定义属性。以这种方式指定的任何属性都会覆盖原型对象上的同名属性。

var anotherPerson = Object.create(person, {    name: {        value: "Greg"    }});alert(anotherPerson.name); //"Greg"

支持 Object.create()方法的浏览器有 IE9+、 Firefox 4+、 Safari 5+、 Opera 12+和 Chrome。
在没有必要兴师动众地创建构造函数,而只想让一个对象与另一个对象保持类似的情况下,原型式继承是完全可以胜任的。

寄生式继承

寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。

function createAnother(original){    var clone = object(original); //通过调用函数创建一个新对象    clone.sayHi = function(){ //以某种方式来增强这个对象        alert("hi");    };    return clone; //返回这个对象}

新对象不仅具有 original的所有属性和方法,而且还有自己的 sayHi()方法。

在主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式。任何能够返回新对象的函数都适用于此模式。

使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率。

寄生式组合继承

组合继承最大的问题就是无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。而两次调用会使实例属性屏蔽原型中的属性。

而寄生组合式继承可以解决这个问题,其思想是通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。

function inheritPrototype(subType, superType){    var prototype = Object(superType.prototype); //创建对象    prototype.constructor = subType; //增强对象    subType.prototype = prototype; //指定对象}

这个函数接收两个参数:子类型构造函数和超类型构造函数。

在函数内部,第一步是创建超类型原型的一个副本。第二步是为创建的副本添加 constructor 属性,从而弥补因重写原型而失去的默认的 constructor 属性。最后一步,将新创建的对象(即副本)赋值给子类型的原型。

只调用了一次 SuperType 构造函数,并且因此避免了在 SubType.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用instanceofisPrototypeOf()。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式

参考材料

《JavaScript高级程序设计》

0 0