JS(7)——继承

继承是OO 语言中的一个最为人津津乐道的概念。许多OO 语言都支持两种继承方式:接口继承和实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。如前所述,由于函数没有签名,在ECMAScript中无法实现接口继承,ECMAScript 只支持实现继承,而且其实现继承主要是依靠原型链来实现的。但原型链的问题是对象实例共享所有继承的属性和方法,因此不适宜单独使用。

1. 原型链

ECMAScript 中描述了原型链的概念,并将原型链作为实现继承的主要方法。

实现原型链有一种基本模式,其代码大致如下。

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

以上代码定义了两个类型:SuperType SubType。每个类型分别有一个属性和一个方法。它们的主要区别是SubType 继承了SuperType,而继承是通过创建SuperType 的实例,并将该实例赋给SubType.prototype 实现的。实现的本质是重写原型对象,代之以一个新类型的实例。换句话说,原来存在于SuperType 的实例中的所有属性和方法,现在也存在于SubType.prototype 中了。在确立了继承关系之后,我们给SubType.prototype 添加了一个方法,这样就在继承了SuperType 的属性和方法的基础上又添加了一个新方法。这个例子中的实例以及构造函数和原型之间的关系如下图所示。



2. 默认的原型

事实上,前面例子中展示的原型链还少一环。我们知道,所有引用类型默认都继承了Object,而这个继承也是通过原型链实现的。大家要记住,所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype。这也正是所有自定义类型都会继承toString()valueOf()等默认方法的根本原因。所以,我们说上面例子展示的原型链中还应该包括另外一个继承层次。下图为我们展示了该例子中完整的原型链。



扫描二维码关注公众号,回复: 1978241 查看本文章

3.组合继承

组合继承(combination inheritance),有时候也叫做伪经典继承,指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。例如:

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

在这个例子中,SuperType 构造函数定义了两个属性:name colorsSuperType 的原型定义了一个方法sayName()SubType 构造函数在调用SuperType 构造函数时传入了name 参数,紧接着又定义了它自己的属性age。然后,将SuperType 的实例赋值给SubType 的原型,然后又在该新原型上定义了方法sayAge()。这样一来,就可以让两个不同的SubType 实例既分别拥有自己属性——包括colors 属性,又可以使用相同的方法了。

组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为JavaScript 中最常用的继承模式。而且,instanceof isPrototypeOf()也能够用于识别基于组合继承创建的对象。

组合继承不足:

在第一次调用SuperType构造函数时,SubType.prototype会得到两个属性:name colors;它们都是SuperType 的实例属性,只不过现在位于SubType 的原型中。当调用SubType构造函数时,又会调用一次SuperType构造函数,这一次又在新对象上创建了实例属性name colors。于是,这两个属性就屏蔽了原型中的两个同名属性。


4. 寄生组合式继承

function object(o){
    function F(){}
    F.prototype = o;
    return new F();
}


function inheritPrototype(subType, superType){

var prototype = object(superType.prototype); //创建对象

prototype.constructor = subType; //增强对象

subType.prototype = prototype; //指定对象

}

 
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);   
};

这个示例中的inheritPrototype()函数实现了寄生组合式继承的最简单形式。这个函数接收两个参数:子类型构造函数和超类型构造函数。在函数内部,第一步是创建超类型原型的一个副本。第二步是为创建的副本添加constructor 属性,从而弥补因重写原型而失去的默认的constructor 属性。最后一步,将新创建的对象(即副本)赋值给子类型的原型。

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

如下图所示:



猜你喜欢

转载自blog.csdn.net/u013789656/article/details/80942629