继承
继承是面向对象编程中很重要的一个概念,继承分两种:接口继承和实现继承。接口继承是继承签名,实现继承是继承属性和方法,因为js函数没有签名,所以在js中无法实现接口继承。js中只能实现继承,下面就总结一下js中几种实现继承方式,以及对应的优缺点。
1、原型链继承
function SuperType() {
this.color = ["yellow", "green", "blue"];
}
SuperType.prototype.getSuperValue = function() {
return this.color;
}
function SuberType() {
this.subproperty = false;
}
SuberType.prototype = new SuperType();
SuberType.prototype.getSuberValue = function() {
return this.subproperty;
}
let instance1 = new SuberType();
console.log(instance1.color); //["yellow", "green", "blue"]
console.log(instance1.getSuperValue()); //["yellow", "green", "blue"]
instance1.color.push("pink");
console.log(instance1.color); //["yellow", "green", "blue", "pink"]
let instance2 = new SuberType();
console.log(instance2.color); //["yellow", "green", "blue", "pink"]
从上述代码的执行,我们可以看出子类SuberType继承了父类SuperType的属性和方法,但是缺点我们可以很明显的看到SuberType的实例修改color属性相互影响,这是因为执行SuberType.prototype = new SuperType()这行代码,就相当于给SuberType.prototype添加了color的属性,SuberType.prototype.color = [“yellow”, “green”, “blue”],以至于所有SuberType的实例都共享了这个color属性。原型链实现继承还有一个问题就是在创建子类实例的我时候无法向父类型的构造函数中传递参数,总结原型链实现继承的优缺点如下。
优点:简单,存粹的继承关系。可以继承父类的属性和方法。
缺点:1、父类中如果存在引用类型,会被所有子类实例所共享,导致子类 实例修改引用类型互相影响。2、子类的的实例无法向父类的构造函数传递参数。
2、借用构造函数
function SuperType(name) {
this.color = ["yellow", "green", "blue"];
this.name = name;
}
SuperType.prototype.getSuperValue = function() {
return this.color;
}
function SuberType(name) {
SuperType.call(this,name)
this.subproperty = false;
}
let instance1 = new SuberType("hello world");
console.log(instance1.color); //["yellow", "green", "blue"]
console.log(instance1.name); //hello world
try {
console.log(instance1.getSuperValue());
}catch(e) {
console.log(e)
}//TypeError: instance1.getSuperValue is not a function
instance1.color.push("pink");
console.log(instance1.color); //["yellow", "green", "blue", "pink"]
let instance2 = new SuberType();
console.log(instance2.color); //["yellow", "green", "blue"]
从上述代码的执行我们可看出,借助构造函数实现继承解决了原型链实现继承的缺陷,子类的实例修改引用类型不会相互影响了,子类的实例也可以向父类的构造函数传递参数了,但是缺点也很明显,子类的实类无法继承父类原型上的属性和方法。还有一个缺点就是借助构造函数模式,方法都在构造函数中定义,无法实现函数的复用,总结一下借助构造函数实现继承的优缺点。
优点:借助构造函数实现继承可以向父类传递参数,通过call多个父类可以实现多继承。
缺点:1、无法继承父类原型对象上的属性和方法。 2、无法实现函数的复用。
3、组合继承
function SuperType(name) {
this.color = ["yellow", "green", "blue"];
this.name = name;
}
SuperType.prototype.getSuperValue = function() {
return this.color;
}
function SuberType(name) {
SuperType.call(this,name); //第二次调用SuperType( )
this.subproperty = false;
}
SuberType.prototype = new SuperType(); //第一次调用SuperType( )
SuberType.constructor = SuberType;
let instance1 = new SuberType("hello world");
console.log(instance1.color); //["yellow", "green", "blue"]
console.log(instance1.name); //hello world
console.log(instance1.getSuperValue());
instance1.color.push("pink"); //["yellow", "green", "blue"]
console.log(instance1.color); //["yellow", "green", "blue", "pink"]
let instance2 = new SuberType();
console.log(instance2.color); //["yellow", "green", "blue"]
从上述代码中我们可以看出,组合继承的思路是使用原型链实现对原型属性和方法的继承,通过构造函数实现对实例属性的继承,这样既通过在原型上定义方法实现函数的复用,又能保证每个实例都有自己的属性,缺点我们也可以看出,无论什么情况下,都会调用两次父类构造函数,第一次是在创建子类实例的时候,另一次是在子类构造函数的内部。子类最终会包含父类的全部实例属性,但是,我们在调用子类构造函数的时候又重写了这些属性。下面我们就总结一下组合继承的优缺点。
优点:弥补了借助构造函数实现继承的不足,可以继承实例属性和方法,也可以继承原型属性和方法,不存在引用类型属性相互影响的问题,函数可复用,也可以传参。
缺点:调用了两次父类的构造函数。
4、寄生组合式继承
function inheritPrototype(subType, superType) {
let prototype = Object(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
function SuperType(name) {
this.color = ["yellow", "green", "blue"];
this.name = name;
}
SuperType.prototype.getSuperValue = function() {
return this.color;
}
function SuberType(name) {
SuperType.call(this,name)
this.subproperty = false;
}
inheritPrototype(SuberType, SuperType);
let instance1 = new SuberType("hello world");
console.log(instance1.color); //["yellow", "green", "blue"]
console.log(instance1.name); //hello world
console.log(instance1.getSuperValue()); //["yellow", "green", "blue"]
instance1.color.push("pink");
console.log(instance1.color); //["yellow", "green", "blue", "pink"]
let instance2 = new SuberType();
从上述代码中我们可以看出寄生组合式继承的基本思路是不通过调用父类构造函数给子类原型赋值,而是取得父类原型的一个副本,inheritPrototype()函数实现了寄生式组合继承的核心。这个函数接收两个参数:子类构造函数和父类构造函数。在函数的内部,先是创建父类原型的一个副本。然后给返回的prototype 对象设置 constructor 属性,解决由于重写原型导致默认 constructor 丢失的问题。最后将新创建的对象赋值给子类型的原型。寄生组合式继承是相对完美实现继承方式,弥补了上面几种实现继承的缺点。