JavaScript面向对象——深入理解寄生组合继承
之前谈到过组合继承,会有初始化两次实例方法/属性的缺点,接下来我们谈谈为了避免这种缺点的寄生组合继承
思路:组合继承中,构造函数继承时已经继承了父类中除了通过prototype定义的公有属性和方法,也就是说我们需要在原型继承时继承父类的原型而不执行父类的构造函数,这样就避免了组合继承中的缺点。
思路有了,如何实现?
让我们从名字入手,寄生组合继承,组合继承我们谈过了,寄生是什么东西?《JavaScript设计模式》这本书中说得比较模糊,因此本人阅读了另一本更专于介绍JavaScript面向对象的书《JavaScript面向对象编程指南第2版》,感觉这本书挺好的,推荐一下:
思路:在创建对象的函数中直接吸收其他对象的功能,然后对其进行扩展并返回。
步骤:
1:设计inheritObject()函数,名称自定,只是举例
设计理念:函数可以用来接收新对象,并返回一个以该对象为原型的新对象
function inheritObject(o) {
function F() {}
F.prototype = o
return new F()
}
2:使用对象标识法定义一个普通对象
var twoD = {
name: '2D shape',
dimensions: 2
}
3:使用inheritObject()函数对twoD进行操作
function triangle(s, h) {
var that = inheritObject(twoD)
that.name = 'Triangle'
that.side = s
that.height = h
that.getArea = function () {
return that.side * that.height / 2
}
return that
}
说明:triangle()中使用inheritObject()将twoD克隆进that对象,并对that对象进行了扩展然后返回。(寄生式继承的思路:在创建对象的函数中直接吸收其他对象的功能,然后对其进行扩展并返回,在此函数中实现。)注意:这个that只是个名称,可以随便换另一个,没有跟保留字this那样的含义。
这种继承方式下创建对象,使不使用new都可以,因为返回的就是一个对象。
// test
var t = triangle(5, 10)
console.log(t.dimensions)
var t2 = new triangle(5, 5)
console.log(t2.getArea())
结果:
寄生组合继承思路回忆:组合继承中,构造函数继承时已经继承了父类中除了通过prototype定义的公有属性和方法,也就是说我们需要在原型继承时继承父类的原型而不执行父类的构造函数,这样就避免了组合继承中的缺点。
实现方案:寄生式继承+组合继承,代码中使用到上边提到的inheritObject()函数。
function inheritPrototype(subClass, superClass) {
// 复制一份父类的原型副本保存在变量中
var p = inheritObject(superClass.prototype)
// 修正因为重写子类原型导致子类的constructor属性被修改
p.constructor = subClass
// 设置子类的原型
subClass.prototype = p
}
解释:因为我们只需要父类原型对象的一个副本,这个副本通过原型继承便可以得到,但是直接赋值给子类是会有问题的,因为对父类对象复制得到的对象p中的constructor指向的不是SubClass子类对象,因此寄生式继承中要修复复制对象p的constructor属性指向不正确的问题,最后得到的复制对象p赋值给子类的原型,这样子类的原型就继承了父类的原型并且没有执行父类的构造函数。
测试例子:
// 定义父类
function SuperClass(name) {
this.name = name
this.colors = ['red', 'orange', 'yellow']
}
// 定义父类原型方法
SuperClass.prototype.getName = function () {
console.log(this.name)
}
// 定义子类
function SubClass(name, time) {
// 构造函数式继承
SuperClass.call(this, name)
// 子类新增属性
this.time = time
}
// 寄生式继承父类原型
inheritPrototype(SubClass, SuperClass)
// 子类新增原型方法, 不可以写在寄生式继承父类原型函数前
SubClass.prototype.getTime = function () {
console.log(this.time)
}
// 创建测试实例
var instance1 = new SubClass('JavaScript', '2018-03-18')
var instance2 = new SubClass('NodeJs', '2018-03-19')
instance1.colors.push('green')
console.log(instance1.colors)
console.log(instance2.colors)
instance1.getName()
instance1.getTime()
instance2.getName()
instance2.getTime()
解释:多数跟组合继承的一样,除了这点:子类原型被赋予了父类原型的一个引用,这是个对象,也就是引用类型,所以给子类添加原型方法时只可以通过prototype.语法,不可以使用subClass.prototype={xxx:function(){}}这种对象赋值式的写法,否则父类的原型会被覆盖。
运行结果终于来了.....
最后来一张寄生组合继承的原理图,理解了的伙伴请忽略,周日愉快,晚安~(2018.03.18)