在ES6引入class关键字之前,要实现类的继承关系就得用原型链的方式来编写。
组合继承
通过原型链实现对原型属性和方法的继承,借用构造函数来实现对实例属性的继承。如下:
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.saySuperName = function() {
alert(this.name);
}
SuperType.prototype.tea = function() {
alert(this.name);
}
function SubType(name, age) {
SuperType.call(this, name);
this.age = age;
}
SubType.prototype = new SuperType();//子类SubType的原型指向父类SuperType的一个实例
SubType.prototype.constuctor = SubType;//重置构造函数指针
SubType.prototype.saySubAge = function() {
alert(this.age);
}
var instance = new SubType("sb", 18);
其中,“重置构造函数指针”是由于上一步的原型对象改变,导致SubType原型对象下的constructor指针指向的是SuperType,所以需要手动重置。
在组合继承中,调用了两次父类的构造函数:
SubType.prototype = new SuperType();//第一次调用,用于子类原型对象的指针所指
SuperType.call(this, name);//当调用SubType的构造函数时,再一次调用了SuperType();
SubType.prototype = new SuperType();//第一次调用,用于子类原型对象的指针所指 SuperType.call(this, name);//当调用SubType的构造函数时,再一次调用了SuperType();
而两次调用,会产生一个问题:子类原型对象上的属性会被子类实例上的属性屏蔽。(由于原型链机制的问题)。如下:
实例instance是SubType类型,它有两个属性name和colors,这两个都源于父类的属性(由于内部调用SuperType.call(this, name)),但是在实例的原型指针_proto_所指向的是SuperType的一个实例(由于调用SubType.prototype =new SuperType()),所以作为父类的一个实例,自然有一份父类的属性name和colors。
而这多出的一份,我们是不需要的。产生这个的原因在于,组合继承是通过父类的一个实例作为子类的一个原型对象。如:子类原型对象->父类实例->父类原型对象。
而我们需要的只是子类原型对象->父类原型对象。
所以寄生组合式继承就由此而生了。
寄生组合式继承
function HeritSuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
HeritSuperType.prototype.sayHeritName = function() {
alert(this.name);
}
HeritSuperType.prototype.echo = function() {
console.log("nothing");
}
function HeritSubType(name, age) {
HeritSuperType.call(this, name);
this.age = age;
}
inheritPrototype(HeritSubType, HeritSuperType);
HeritSubType.prototype.sayHeritAges = function() {
alert(this.age);
}
var heritInstance = new HeritSubType("2b", 20);
function inheritPrototype(subType, superType) {
let prototype = Object.create(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
其中:inheritPrototype(subType, superType)函数是寄生式继承函数,以来包装父类和子类。将父类的原型对象取出,父类原型对象的构造指针指向子类,再由子类的原型对象指向父类原型对象,从而达到子类原型对象->父类原型对象。
注:如组合继承一样,如果不将父类原型对象的构造指针指向子类,就会导致子类原型对象和父类原型对象的构造指针一样。
为了进行更直观的对比,实例化一个父类对象:
var testHeritSuper = new HeritSuperType("3b");//寄生组合式继承的父类实例
可以看到,父类实例的原型对象是object,拥有自己的一份数据。heritInstance的原型对象是HeritSuperType,这里是用浅复制(Object.create),所以在其原型对象上不会有数据。所以后续给原型添加新方法,会存在(HeritSubType.prototype.sayHeritAges)。
结语
寄生组合式继承,才有其他面向对象语言中的继承意味。如C#中的:、Java中的extend。当然在ES6,JS加入了extend关键字,但这不意味着以原型链方式做出的继承关系就可以不用了解。