概述
-
为什么需要继承?
面向对象编程主要目标:复用性、灵活性和扩展性。复用即一些属性或者方法可以被多个对象实例共享。JavaScript中所有引用类型的数据类型都会继承 Object ,如果没有继承,意味着每个数据类型都需要自行实现一些基础的方法,例如 toString。
-
常见的继承方式?
继承一般有:接口继承、实现继承。接口继承只继承方法的签名;实现继承则继承实际的方法。而Javascript中的使用的就是实现继承。
关于继承的讨论可以参考:
JavaScript的基础方式
1. 原型继承
核心问题:原型继承的原理是什么?
借助原型链概念,实现属性和方法的继承:
function SuperType(){
this.property = true;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){
this.subproperty = false;
}
//继承了 SuperType, 核心点
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
return this.subproperty;
};
var instance = new SubType();
instance.colors.push("black");
alert(instance.getSuperValue()); //true
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green,black" // 引用类型已经被修改
原型链继承即是子函数的原型为父函数的实例,本质上扩展了对象的原型搜索机制。

缺陷
最主要的问题来自包含引用类型值的原型。包含引用类型值的原型属性会被所有实例共享;而 这也正是为什么要在构造函数中,而不是在原型对象中定义属性的原因。通过原型来实现继承时,原 型实际上会变成另一个类型的实例。于是,原先的实例属性也就顺理成章地变成了现在的原型属性。
其次在创建子类型的实例时,不能向超类型的构造函数中传递参数。
2. 组合继承
核心问题: 既然是组合,那组合了哪些内容?
组合继承指的是将原型继承 和 **构造函数借用(constructor stealing)**组合到一块实现属性和方法的继承
构造函数借用:是指通过call 或者 apply 在子函数中调用父函数
function SuperType(){
this.colors = ["red", "blue", "green"];
}
function SubType(){
//借用 SuperType
SuperType.call(this);
}
使用组合继承,通过借用构造函数,结合原型属性屏蔽特征,有效解决了原型继承的缺点。但存在一个问题:
无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是 在子类型构造函数内部。没错,子类型最终会包含超类型对象的全部实例属性,但我们不得不在调用子 类型构造函数时重写这些属性。
3. 寄生式继承
核心问题:寄生式继承,谁寄生,怎么寄生的?
回顾一下原型链继承,子函数需要使用 SubType.prototype = new SuperType(), 导致SuperType属性和原型上的方法都存在于 SubType 原型了,本意应该只是继承父函数的原型。也就是说 SubType.prototype 应该只与 SuperType.prototype 的有关系就好了。
实现方式可以有:
SubType.prototype = Object.create(SuperType.prototype)
// 或者使用其适配
function createObject(prototype) {
function F() {}
F.protoytpe = prototype
return new F()
}
SubType.prototype = createObject(SuperType.prototype)
这样就是实现父子函数的原型的桥接,也就是这里体现的所谓的寄生?:需要被继承的对象寄生于一个被创建的对象上,看以下示例可能更好理解些:
function createParasiticObject(parent) {
const child = Object.create(parent)
child.sayHi = function () {
alert('hi')
}
return child
}
const original = {
sayHello() {console.log('hello')}
}
const instance = createParasiticObject(original)
instance.sayHi()
instance.sayHello()
也就是被继承的对象original 通过原型的方式直接寄生到 instance 上面了,该过程和构造函数区别应该就是不需要使用 new 来生成一个对象了吧。 好难理解。。强行解释了一波。
4. 组合寄生式继承
这个就比较好理解了,主要就是解决组合继承中存在的两次构造函数的调用问题,即原型通过寄生式继承完成,然后组合 借用构造函数 方式完成对属性的继承。
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
SuperType.call(this, name);
this.age = age;
}
SubType.prototype = Object.create(SuperType.prototype);
SubType.prototype.sayAge = function(){
alert(this.age);
};
总的来说寄生组合式继承,集寄生式继承和组合继承的优点与一身,是实现基于类型继承的最有效方式。
以上总体概念以及相关介绍来源于《JavaScript高级程序设计》第三版,重新阅读还是有些新的收获