javascript 的七种继承方式(六)寄生组合式继承

1,387 阅读3分钟

这是我参与8月更文挑战的第11天,活动详情查看:8月更文挑战

组合继承回顾

前面的文章已经提到过,组合继承是JavaScript中最常用的继承模式。但是他也有自己的不足。组合继承最大的问题就是不管在什么情况下,都会调用两次父类型的构造函数:一次是在创建子类型原型的时候,另一次是在子类型内部。子类型最终会包含父类型对象的全部实例属性,但我们不得不在调用子类型构造函数时重写这些属性。下面再来看一下组合继承的例子:

function Father(name){
    this.name = name;
    this.friends = ['Yannis','Lucy']
}

Father.prototype.sayName = function(){
    console.log(this.name);
}

function Son(name, age){
    Father.call(this,name);//第二次调用Father()
    this.age = age;
}

Son.prototype = new Father();//第一次调用Father()
Son.prototype.constructor = Son;
Son.prototype.sayAge = function(){
    console.log(this.age)
}

上面代码中加注释的部分是调用Father构造函数的代码。在第一次调用Father构造函数时,Son.prototype会得到两个属性:name和friends;它们都是Father的实例属性,只不过是现在位于Son 的原型中。当调用Son的构造函数时,又会再一次调用Father的构造函数,这一次又在新对象中创建了实例属性name和friends。最终这两个属性就屏蔽了原型中的两个同名的属性。如下图所示描述了上述过程。

 如上图所示,有两组name和friends属性:一组在实例上,一组在Son的原型中。这就是调用两次Father构造函数的结果。好在已经找到了这个问题的解决方法,就是接下来我们要介绍的:寄生组合是继承

寄生组合式继承

所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。其背后的基本思路是:不必为了子类型的原型而调用父类型的构造函数,我们所需的无非是要父类型原型的一个副本而已。本质上就是使用寄生式继承来继承父类型的原型,然后再将结果指定给子类型的原型。下面代码展示了寄生组合式继承的基本模式:

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

function inheritPrototype(son,father){
    var prototype = object(father.prototype);//创建对象
    prototype.constructor = son;//增强对象
    son.prototype = prototype;//指定对象
}

在这个例子中inheritPrototype()函数实现了寄生组合式继承的最简单形式。这个函数接收两个参数:子类型构造函数和父类型的构造函数。在函数内部,第一步是创建父类型原型的一个副本。第二步是为创建的副本添加constructor属性,从而弥补因重写原型而失去的默认的constructor属性。最后一步就是将新创建的副本赋值给子类型的原型。这样我们就可以调用inheritPrototype()函数来替换上面例子中为子类型原型赋值的语句了,如下:

function Father(name){
    this.name = name;
    this.friends = ['Yannis','Lucy']
}

Father.prototype.sayName = function(){
    console.log(this.name);
}

function Son(name, age){
    Father.call(this,name);//第二次调用Father()
    this.age = age;
}

//Son.prototype = new Father();//第一次调用Father()

inheritPrototype(Son,Father); //新语句
//或者直接用Object.create()也可以实现,效果是一样的
//Son.prototype = Object.create(Father.prototype);
//Son.prototype.constructor = Son;

Son.prototype.sayAge = function(){
    console.log(this.age)
}

这个例子的高效率体现在它只调用了一次Father构造函数,并且因此避免了在Son.prototype上创建不必要的多余的属性。与此同时,原型链还能保持不变。

这就是寄生式组合继承,也是目前相对来说比较好的继承方式,在下一篇中我们将继续学习es6中新增的类的继承。