以下理解基于《JavaScript高级语言程序》第四版,笔者能力有限,理解错误的地方欢迎指出。
前置概念
原型链继承
主要就是通过修改构造函数的Prototype属性,到另一个对象(这个对象也可以是其它对象的子类实例),从而完成继承。
缺点:当父类属性中的值是引用数据类型时,子类实例修改了这个继承下来的属性,他会对父类和所有子类的这个属性值都产生影响。
function SuperType() {
this.colors = ['red','blue','black']
}
SuperType.prototype.getColors = function () {
return this.colors
}
function SubType() {}
SubType.prototype = new SuperType()
const sub1 = new SubType()
sub1.colors.push('azure')
const sub2 = new SubType() //['red', 'blue', 'black', 'azure']
盗用构造函数
其实,属性通常会定义在构造函数中而不是原型对象上,就是因为原型链继承的这个缺点。那么,只要在子类的构造函数中重新定义一下和父类原型属性完全一样的属性就可以了。所以我们在子类构造函数中,调用一下父类的构造函数就可以达到目标:
function SubType(arg1){
SuperType.call(this,arg1)
}
并且还能够向父类的构造函数传参
缺点:不能够访问到原型链中的方法,并且因为必须在构造函数中定义方法,所以函数不能够重用。
组合继承
原型链继承和盗用构造函数都有缺点,但是他们的缺点并不重叠,所以我们可以将两种方式叠加起来,就形成了组合继承。
SubType.prototype = new SuperType() //用来继承原型链上的方法
SuperType.call(this,arg1) //用来继承原型上的属性。
缺点:也有缺点,存在效率问题。这种方式会调用两次父类构造函数。
原型式继承
原型式继承可以描述为一个函数,这个函数的基本原理是依赖原型链继承来的。
function object(obj){
function Func(){}
Func.prototype = obj
return new Func()
}
以上是原型式继承的主要原理,他在函数内部创建了一个临时的构造函数,函数返回的是一个继承了入参obj
的对象。原型式继承的好处就是无需调用构造函数。但是原型链继承的缺点依旧存在。
寄生式继承
寄生式继承几乎和原型式继承差不多,可以把它看成是原型式继承的加强版,加强的地方就是在拿到通过原型式继承返回的对象之后,在这个对象上可以进行额外的操作。
function createAnother(original){
const clone = object(original) //objct()就是原型式继承的实现
clone.sayHi = function (){ //额外操作
console.log('Hi')
}
}
注意的是:寄生式继承内部的逻辑并不必须与原型式继承相关,只要是一个返回对象的操作都可以。寄生式继承的逻辑重点在于,对返回回来的这个函数进行额外的操作处理。(严格来说“寄生式继承几乎和原型式继承差不多”这一段的理解并不对,但是便于理解)
寄生式组合继承
产生这种继承方式的主要原因就是组合继承的缺点:需要调用两次父类构造函数。
组合式继承其中非常重要的一步:盗用构造函数
,就是想要返回一个和父类属性一模一样的属性。但是原型式继承
也可以达到相同的效果,并且并不需要调用构造函数。于是,寄生式组合继承的基本思路就是用原型式继承来替换子类构造函数中的盗用构造函数
function inheritPrototype(SuperType,SubType){
let prototype = object(SuperType) //objct()就是原型式继承的实现
prototype.constructor = SubType // 增强对象【寄生式继承】
SubType.prototype = prototype // 赋值对象
}
代码的后面两步可以简单理解为:构造函数和原型对象之间进行连接。
寄生式组合继承可以算得上是引用类型继承的最佳模式【P249】