面向对象的程序设计(二)

来源:互联网 发布:好的网络兼职 编辑:程序博客网 时间:2024/06/08 06:01

三、继承

许多面向对象的语言都支持两种继承方式:

  • 接口继承:只继承方法签名。
  • 实现继承:继承实际的方法。

由于函数没有签名,在ECMAScript中无法实现接口继承。ECMAScript只支持实现继承,而且其实现继承主要是依靠原型链来实现。

1、原型链

1)利用原型链实现继承的基本思想

利用原型让一个引用类型继承另一个引用类型的属性和方法。

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

上面的代码定义了两个类型:SuperType和SubType、每个类型分别有一个属性和一个方法。其中,SubType继承了SuperType,而继承是通过创建SuperType的实例,并将该实例赋给SubType.prototype实现的。实现的本质是重写原型对象,代之以一个新类型的实例。在这个例子中,instance指向SubType的原型,SubType的原型又指向SuperType的原型。

通过原型链实现继承的情况下,搜索过程沿着原型链向上搜索。以上面的例子为例,调用instance.getSuperValue()会经历三个搜索过程:搜索实例→搜索SubType.prototype→搜索SuperType.prototype,在最后一步才找到该方法。在找不到属性或方法时,搜索过程总是要一环一环地前行到原型链末端才会停下来。

2)默认的原型

所有引用类型默认都继承Object,这个继承是通过原型链实现的。
所有函数的默认原型都是Object的实例,默认原型都会包含一个内部指针,指向Object.prototype。

3)构造函数、原型和实例的关系

每个构造函数都有一个原型对象,原型对象包含一个指向指针构造函数的指针,而实例都包含一个指向原型对象的内部指针。

确定原型和实例之间关系的方式:

  • 使用instanceof操作法:用这个操作符来测试实例与原型链中出现过的构造函数,结果返回true。
  • 使用isPrototypeOf()方法:只要是原型链中出现过的原型,都可以说是该原型链所派生的实例原型,会返回true。

4)注意事项

  • 给原型添加方法的代码一定要放在替换原型的语句之后。
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());  // false
  • 在通过原型链实现继承时,不能使用对象字面量创建原型方法。因为这样做会重写原型链。
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

5)原型链的问题

  • 在通过原型来实现继承时,原型实际上就会变成另一个类型的实例。原先的实例属性就变成了现在的原型属性。比如下面这个例子中,SubType的所有实例都会共享这一个colors属性。
function SuperType(){    this.colors = ["red", "blue", "green"];}function SubType(){}SubType.prototype = new SuperType();var c1 = new SubType();c1.colors.push("black");alert(c1.colors);  // red,blue,green,blackvar c2 = new SubType();alert(c2.colors);  // red,blue,green,black
  • 在创建子类型的实例时,不能向超类型的构造函数中传递参数。

2、借用构造函数(伪造对象或经典继承)

1)基本思想:在子类型构造函数的内部调用超类型构造函数。

function SuperType(){    this.colors = ["red", "blue", "green"];}function SubType(){    //继承了SuperType    SuperType.call(this);}var c1 = new SubType();c1.colors.push("black");alert(c1.colors);  // red,blue,green,blackvar c2 = new SubType();alert(c2.colors);  // red,blue,green

2)优势(相对于原型链):可以在子类型构造函数中向超类型构造函数传递参数。

function SuperType(name){    this.name = name;}function SubType(){    //继承了SuperType,同时还传递了参数    SuperType.call(this,"T");    //实例属性    this.age = 12;}var p = new SubType();alert(p.name);  //Talert(p.age);  // 12

3)问题:

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

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.sayAge = function(){    alert(this.age);};var instance1 = new SubType("T", 29);instance1.colors.push("black");alert(instance1.colors);  //"red,blue,green,black"instance1.sayName();      //"T";instance1.sayAge();       //29var instance2 = new SubType("Greg", 27);alert(instance2.colors);  //"red,blue,green"instance2.sayName();      //"Greg";instance2.sayAge();       //27

4、原型式继承

借助原型可以基于已有的对象创建新对象,不必创建自定义类型。
看下面的代码,在object()函数内部,先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。

function object(o){    function F(){}    F.prototype = o;    return new F();}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"

ECMAScript 5 通过新增Object.create()方法规范化了原型式继承。
该方法接收两个参数:一个是用作新对象原型的对象和(可选)一个新对象定义额外属性的对象。

//传入第一个参数var person = {    name: "Nicholas",    friends: ["Shelby", "Court", "Van"]};var anotherPerson = Object.create(person);anotherPerson.name = "Greg";anotherPerson.friends.push("Rob");var yetAnotherPerson = Object.create(person);yetAnotherPerson.name = "Linda";yetAnotherPerson.friends.push("Barbie");alert(person.friends);   //"Shelby,Court,Van,Rob,Barbie"//传入第二个参数var person = {    name: "Nicholas",    friends: ["Shelby", "Court", "Van"]};var anotherPerson = Object.create(person, {    name: {        value: "Greg"    }});alert(anotherPerson.name);  //"Greg"

5、寄生式继承

与寄生构造函数和工厂模式类似,创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后返回对象。

function createAnother(original){    var clone = Object(original);   //通过调用函数创建一个新对象    clone.sayHi = function(){       //以某种方式来增强这个对象        alert("hi");    };    return clone;                   //返回这个对象}var p1 = {    name: "T",    friends: ["A", "B", "C"]};var p2 = createAnother(p1);p2.sayHi();  // hi

6、寄生组合式继承

1)组合继承的问题

无论什么情况下,都会调用两次超类型构造函数,一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。

2)寄生组合式继承。是指通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。
基本思路是:不必为了指定子类型的原型而调用超类型的构造函数,我们需要的只是超类型原型的一个副本而已。

//寄生组合式继承的基本模式function inheritPrototype(subType,superType){    var prototype = Object(superType.prototype);    //创建对象    prototype.constructor = subType;                //增强对象    subType.prototype = prototype;                  //指定对象}