总结一下组合继承是什么。
JavaScript 红宝书上为了更好地梳理知识,写明原委,所以就写的比较繁杂,由头到尾说了好几种继承方式,包括:原型链继承、借用构造函数继承、组合继承、原型式继承、寄生式继承、寄生组合式继承,其实我们在工作的过程中,已经固定使用组合式继承了,因为这种继承方式相对是最优的方式了,所以最常用。
下面分别介绍一下几种继承方式。
一、原型链继承
function SuperType() {
this.superProperty = true
this.colors = ["red", 'blue']
}
SuperType.prototype.getSuperValue = function () {
return this.superProperty
}
function SubType() {
this.subProperty = false
}
SubType.prototype = new SuperType()
SubType.prototype.getSubProperty = function () {
return this.subProperty
}
上述代码就是原型链继承,通过把子类的原型指向父类的一个实例,父类的实例对象里又会自动指向父类的原型,成功地将子类和父类链接了起来,实现了继承,但是很明显有两个问题:
1、子类实例有来自包含引用类型值的原型,如上述代码中的 colors,该 colors 出现的位置是在子类实例的原型对象上,所以如果有两个子类实例,sub1 和 sub2,那么他们的 colors 指向的是同一个地址,当用 sub1 去修改 colors 的时候,sub2 中的 colors 也会同时修改。
2、创建子类实例时并不能向父类的构造函数中传递参数。
二、借用构造函数
function SuperType(superValue) {
this.superProperty = superValue
this.colors = ["red", 'blue']
}
function SubType(subValue, superValue) {
SuperType.call(this, superValue)
this.subProperty = subValue
}
上述代码就是借用构造函数实现继承,实际上就是巧妙地在子类里面使用 call(或者 apply)方法调用了父类构造函数,因为构造函数也是函数,通过这样的调用就成功地规避了上述原型链的方式,第一就是每个子类实例的 colors 指向了不同的地址,第二就是还可以向父类构造函数中传递参数,但是如果完全使用父类构造函数在子类构造函数中调用,而不考虑原型链的话,也会存在很多问题,最明显的就是如何设置共享的方法呢?如果在父类构造函数中定义函数方法,就会导致每个实例都有自己的这个函数,100个实例就会创建100个相同的函数,极大地浪费了内容,因为函数是可以复用的,并不需要每个实例都创建一遍。所以自然而然有了下面的组合继承。
三、组合继承--常用的继承方式
如上所述,组合继承就是综合了原型链继承和借用构造函数继承,通过原型链继承去实现对原型属性和方法的继承,通过借用构造函数继承去实现对实例属性的继承,代码如下:
function SuperType(superValue) {
this.superProperty = superValue
this.colors = ["red", 'blue']
}
SuperType.prototype.getSuperProperty = function () {
return this.superProperty
}
function SubType(subValue, superValue) {
SuperType.call(this, superValue)
this.subProperty = subValue
}
SubType.prototype = new SuperType()
SubType.prototype.getSubProperty = function () {
return this.subProperty
}
组合继承是最常用的继承模式。
4、原型式继承
这种继承方法没有使用严格意义上的构造函数,他的思想是借助原型可以基于已有的对象创建新对象,还不用因此创建自定义类型。有一个核心函数如下:
function object(o) {
function F() { }
F.prototype = o
return new F()
}
应用如下:
function object(o) {
function F() { }
F.prototype = o
return new F()
}
const person = {
name: "Mary",
friends: ['Tom', 'Linda']
}
const anotherPerson1 = object(person)
anotherPerson1.name = "Bela"
anotherPerson1.friends.push("LiHua")
const anotherPerson2 = object(person)
anotherPerson2.name = "Smith"
anotherPerson2.friends.push("LiMing")
// person.friends的内容变为 ['Tom', 'Linda',"LiHua",'LiMing']
该函数从本质上来讲,就是对传入的对象进行了一个浅复制,并且必须有一个对象作为另一个对象的基础,看到这里大家是不是很自然地想到了 Object.create() 函数呢?在 ESMASCRIPT5 规范中,就用 Object.create 规范了该原型式继承。不过在使用的过程中需要特别注意引用类型,如上述的 friends 属性就指向同一块地址。
五、寄生式继承
寄生式继承和原型式继承是紧密相关的一种思想,都是同一个人提出来的,寄生式继承会创建一个仅用于封装继承过程的函数,在该函数内部以某种方式增强对象。
function createAnother(o) {
const clone = object(o)
clone.sayHi = function () {
console.log("hello")
}
return clone
}
通过这样的模式,继承的对象就可以创建自己的属性和方法。
六、寄生组合式继承--理想的继承范式
组合继承是最常用的继承模式,但是他也有不足的地方,仔细观察上面组合继承的代码,可以发现我们调用了两次父类的构造函数,这样会产生什么后果呢?那就是一个子类实例会出现两层相同的属性,第一层就是在实例本身上会有 SuperType 中定义的实例属性,即上面的 superProperty 属性,第二层就是在实例的原型上还会有 superProperty 属性,值为 undefined,怎么解决这个问题呢?那就是使用寄生式继承的思想来创建子类的原型,我们定义一个函数如下:
function inheritPrototype(subType, superType) {
let prototype = object(superType.prototype)
prototype.constructor = subType
subType.prototype = prototype
}
整体代码如下:
function SuperType(superValue) {
this.superProperty = superValue
this.colors = ["red", 'blue']
}
SuperType.prototype.getSuperProperty = function () {
return this.superProperty
}
function SubType(subValue, superValue) {
SuperType.call(this, superValue)
this.subProperty = subValue
}
inheritPrototype(SubType,SuperType)
SubType.prototype.getSubProperty = function () {
return this.subProperty
}
这样就不会再调用两次构造函数了,子类实例也就不会出现两次相同的实例属性了,而这也是最理想的继承范式了。
以上内容均来自 JavaScript 红宝书的继承章节,最后附上现在我们最常用的继承代码:
1、使用函数形式:
function SuperType(superProperty) {
this.superProperty = superProperty
}
SuperType.prototype.getSuperProperty = function () {
return this.superProperty
}
function SubType(superProperty, subProperty) {
SuperType.call(this, superProperty)
this.subProperty = subProperty
}
SubType.prototype = Object.create(SuperType.prototype)
SubType.prototype.constructor = SubType
SubType.prototype.getSubProperty = function () {
return this.subProperty
}
2、使用class extends 形式:(其实是函数形式的语法糖)
class SuperType {
constructor(superProperty) {
this.superProperty = superProperty
}
getSuperProperty() {
return this.superProperty
}
}
class SubType extends SuperType {
constructor(superProperty, subProperty) {
super(superProperty)
this.subProperty = subProperty
}
getSubProperty() {
return this.subProperty
}
}