带你看懂JavaScript的继承(八) 寄生组合式继承

我们讲过组合式继承,可以说是我们最常用的一种继承。但是组合式继承也是有一个缺点的,今天我们就仔细的扣一扣这个容易被忽略的点。我们先回顾一下组合式继承的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function SuperType(name){  
this.name = name;
this.colors = ["red","blue"];
}

SuperType.prototype.sayName = function(){
console.log(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(){
console.log(this.age);
}

大家是不是看了注释一眼就明白了,组合式继承中,我们调用了两次超类型的构造函数。第一次调用的时候,子类型的获得了超类型实例的的两个属性(这里我们不谈那个方法):name和colors,只不过是他们是存在于子类型的原型里。当调用子类型SubType的构造函数时,会产生对超类型SuperType构造函数的第二次调用,这一次,在新的对象上生成了name和colors两个属性,这两个属性会屏蔽原型上的两个同名属性。

说简单一点就是,这两次调用,第一次是在原型上生成了这两个属性,第二次是在新的实例上生成了这两个属性。通过原型链我们知道,实例上的属性在调用时会屏蔽原型上的同名属性。所以每个实例都有了自己的name和colors。
这里有两组name和colors属性,一组在实力上,一组在子类的原型中。这就是调用两次超类构造函数的后果。

那么我们怎么解决这个问题————用寄生组合式继承。原理是:通过借用构造函数继承属性,通过原型链的混成形式来继承方法。通过上一篇的思想,我们可以得出一个结论,我们不必为了指定子类型的原型而调用超类型的构造函数,我们可以直接使用超类型的一个副本。具体的操作就是,我们使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。

1
2
3
4
5
6
7
8
9
10
11
function object(o){  
function F(){}
F.prototype = o;
return new F();
}

function inheritPrototype(superType, subType){
var prototype = object(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}

这里我们封装了寄生式组合继承的一个方法。inheritPrototype()接收连个参数,超类型和子类型的构造函数。我们首先通过object方法创建一个超类型原型的副本。然后,我们将这个副本的constructor指向子类型,这是为了弥补我们重写原型而造成的默认constructor丢失。最后,我们将这个新创建的原型副本赋值给子类型的原型,这样我们就可以再子类型里调用超类型原型上的方法。
下面我们看一个实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function SuperType(name){  
this.name = name;
this.colors = ["red","blue"];
}

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

function SubType(name, age){
SuperType.call(this.name);
this.age = age;
}

inheritPrototype(SuperType, SubType);

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

这样,我们得到了以下几个好处:

  1. 只调用了一次SuperType构造函数
  2. SubType的原型上不存在多余的同名属性
  3. 保证了原型链的完整
  4. 能够正常使用instanceOf和isPrototypeOf()方法

可以说到这里,我们解决了我们前面所提到的所有缺点和问题。寄生组合式继承是开发啊这普遍认为的最理想的javascript继承实现方式。

讲到这里,我们的“带你看懂javascript的继承”系列也就面临完结了,下次的最后一期,我们做一下拾遗,稍微做一下拓展。在此,希望我们都能在这一个系列中有所得,初步掌握原型以及基于原型实现的继承方法。起码在面试的时候,再也不用担心面试官问你继承了。