JavaScript继承(三)——组合继承中讲到,组合继承是JavaScript中最常用的继承模式,但是它也有自己的不足之处,现在我们就来剖析它的不足,如下示例:
function Human(name){
this.name = name;
this.colors = ['white', 'yellow', 'black'];
}
Human.prototype.sayName = function(){
return this.name;
}
function Person(name, job){
//继承属性
Human.call(this, name);
//定义自己的属性
this.job = job;
}
//继承方法
Person.prototype = new Human();
Person.prototype.constructor = Person;
//定义自己的方法
Person.prototype.sayJob = function(){
return this.job;
}
let p = new Person('bob', 'JavaScript');
console.log(p.sayName());//bob
使用组合继承让Person
继承Human
实际上分为两步:
Human.call(this, name)
继承属性。Person.prototype = new Human()
继承方法。
但是在第二步中,当我们为了继承方法而创建父类型的实例时,执行了一遍构造函数,将属性也初始化了,如下图所示:
然而并不需要这样做,我们是想使用原型上的方法,属性的继承完全可以留待new Person('bob', 'JavaScript')
时通过Human.call(this, name)
来调用父类型的构造函数,这里new Human()
等于事先多调用了一次Human
的构造函数,影响了代码的效率。
再者,即便父类型已经初始化了属性,待将来创建子类型的对象,子类型对象的属性也会屏蔽父类型已经初始化好的属性,也就是说new Human()
初始化的属性根本就没什么用处,如下图所示:
p
上的colors
和name
会屏蔽掉原型上的colors
和name
, 那么有没有一种方案可以实现只使用原型上的方法,而不用调用父类型的构造函数呢?方案是有的,这就是本篇文章要讲的寄生组合式继承。
我们可以直观地想一下,既然只是想使用原型上的方法,不调用构造函数,那么直接将父类型的原型赋给子类型的原型不就可以了吗?像下面这样:
//继承方法
Person.prototype = Human.prototype;
表面上看是没什么问题,假如要用子类型对象调用sayName
方法时,对象上没有,于是到原型去找,原型就是Human
的原型,于是找到了sayName
方法,成功调用。但是结合原型链和对象深浅复制的知识(可以参考JavaScript继承(一)——原型链和JavaScript中对象的浅复制和深复制),Human
本身就是对象,Human.prototype
其实是Human
原型对象的引用,Person.prototype = Human.prototype
就是引用的复制,结果就是Person.prototype
和Human.prototype
指向同一个对象——Human
的原型对象,而原型对象的constructor
属性是指向函数的,那么现在我们让Human
原型对象的constructor
属性指向谁呢?Human
还是Person
?显然原型链被打乱了。想起了哪吒的三头六臂,要是再有一个constructor
属性就好了。
这时原型式继承又派上用场了,如下所示:
//继承方法
Person.prototype = object(Human.prototype);
Person.prototype.constructor = Person;
我们可以使用原型式继承创建Human
原型对象的一个子对象,这个子对象赋给Person.prototype
,改变这个子对象的constructor
为Person
,这样既可以达到Person
的原型继承Human
的原型的效果,同时还不必修改Human
原型的constructor
属性,也不需要调用Human
的构造函数,完美地解决了我们的问题。
下面我们来看下现在的原型链图:
我们可以定义一个函数来实现这个逻辑:
function inheritPrototype(subType, superType){
subType.prototype = Object.create(superType.prototype);
subType.prototype.constructor = subType;
}
于是上面的组合继承可以改写为:
function Human(name){
this.name = name;
this.colors = ['white', 'yellow', 'black'];
}
Human.prototype.sayName = function(){
return this.name;
}
function Person(name, job){
//继承属性
Human.call(this, name);
//定义自己的属性
this.job = job;
}
//继承方法
inheritPrototype(Person, Human);
//定义自己的方法
Person.prototype.sayJob = function(){
return this.job;
}
let p = new Person('bob', 'JavaScript');
console.log(p.sayName());//bob
继承方法时使用我们自己封装的inheritPrototype
方法,这就是寄生组合式继承。
寄生组合式继承的高效率体现在它只调用了一次父类型的构造函数,并且因此避免了在子类型的原型上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用instanceof
和isPrototypeOf()
。开发人员普遍认为寄生组合式继承是引用类型最理想的继承模式。