许多OO 语言都支持两种继承方式:

  • 接口继承:只继承方法签名

  • 实现继承:继承实际的方法。

由于函数没有签名,在ECMAScript 中无法实现接口继承。ECMAScript 只支持实现继承

原型链继承

原型链是Javascript实现继承的主要方法

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();
console.log(instance.getSuperValue())//true

//instanceof用于测试实例与原型链中出现过的构造函数的关系
console.log(instance instanceof object) //true
console.log(instance instanceof SuperType) //true
console.log(instance instanceof SubType) //true

console.log(Object.prototype.isPrototypeof(instance)) //true
console.log(SuperType.prototype.isPrototypeof(instance)) //true
console.log(SubType.prototype.isPrototypeof(instance)) //true

实践中较少单独使用原型链继承

注意:

  1. instance.constructure现在指向的是SuperType

  2. instance.toString现在指向的是Object.prototype

  3. SuperType中的引用值类型会在所有SubType中共享。

  4. 创建SubType实例时,无法向SuperType的构造函数传递参数(在不影响所有对象实例的情况下)

易错点

  1. SubType、SuperType添加原型方法的代码要放在替换原型的语句之后。否则SubType添加的原型方法会失效,因为原型对象被替换了。

  2. 不能通过字面量创建SubType的原型对象。因直接对原型赋值字面量对象会替换SubType的原型,打破了原型链。

借用构造函数继承

解决了Javascript原型链继承中引用值类型被共享的问题

function SuperType(){
    this.colors=["red","blue","green"];
}
function SubType(){
    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(this)apply(this)我们再SubType的实例环境下调用了SuperType的构造函数,因此在SubType对象上执行了SuperType的对象初始化代码,因此每个SubType都具有了自己的colors属性

优势

  1. 可以传递在子类构造函数中向超类构造函数传递参数

问题

  1. 因为方法都在构造函数中定义的,因此无法实现函数复用,每个对象都重新定义了一次方法。

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

注意

  1. 为了确保SuperType构造函数不会重写子类属性,应在调用SuperType的构造函数之后再添加子类自身的属性。

组合继承

将原型链和借用构造函数组合到一块,发挥二者之长的一种继承模式。

  • 原型链实现对属性和方法的复用。

  • 借用构造函数实现对实例属性的继承。

function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
    console.log(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(){
    console.log(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
console.log(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";

instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
console.log(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27

原型式继承

原型式继承并没有使用严格意义上的构造函数。是借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。

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

在object()函数内部,先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。从本质上讲,object()对传入其中的对象执行了一次浅复制。
ECMAScript 5 通过新增Object.create()方法规范化了原型式继承。这个方法接收两个参数:

  1. 一个用作新对象原型的对象和(可选的)

  2. 一个为新对象定义额外属性的对象。

在传入一个参数的情况下,Object.create()object()方法的行为相同。
Object.create()方法的第二个参数与Object.defineProperties()方法的第二个参数格式相同:每个属性都是通过自己的描述符定义的。以这种方式指定的任何属性都会覆盖原型对象上的同名属性。
在没有必要兴师动众地创建构造函数,而只想让一个对象与另一个对象保持类似的情况下,原型式继承是完全可以胜任的。

寄生式继承

创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象

function createAnother(original){
    //通过调用函数创建一个新对象,任何能够返回新对象的函数都适用该模式
    var clone = object(original); 
        clone.sayHi = function(){ //以某种方式来增强这个对象
        alert("hi");
    };
    return clone; //返回这个对象
}
var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"

问题

  1. 无法做到函数复用而降低效率

寄生组合式继承

由于组合继承两次调用了SuperType的构造函数,SubType就具有了两组SuperType的属性:一组在实例上,一组在SubType 原型中。
寄生组合式继承通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。
基本思路:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。
本质:使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。

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

优势

  1. 高效率:只调用了一次SuperType 构造函数,并且因此避免了在SubType.prototype 上面创建不必要的、多余的属性。

  2. 原型链还能保持不变;因此,还能够正常使用instanceof 和isPrototypeOf()

  3. 寄生组合式继承是引用类型最理想的继承范式。

  4. YUI 的YAHOO.lang.extend()方法采用了寄生组合继承,从而让这种模式首次出现在了一个应用非常广泛的JavaScript 库中。

node中的util.inherits继承

寄生组合式继承与寄生组合式继承中inheritPrototype功能一致,因此如果不希望在子类间共享引用类型值属性,还需组合借用构造函数继承。

exports.inherits = function(ctor,superCtor){
    ctor.super_ = superCtor;
    ctor.prototype = Object.create(superCtor.prototype,{
        constructor:{
            value:ctor,
            enumerable:false,
            writable:true,
            configurable:true
        }
    };
};

abc126
19 声望0 粉丝