}
// 声明子类
function SubClass(id){
SuperClass.call(this,id);
}
注意,SuperClass.call(this,id) 这条语句是构造函数式继承的精华,由于call这个方法可以改变函数的作用环境,因此在子类中,对SuperClass调用这个方法就是将子类的变量在父类中执行一遍,由于父类是给this绑定属性的,因此子类自然也就继承了父类的共有属性。但是这种类型的继承并没有涉及原型prototype,所以父类的原型方法自然不会被继承。
// 组合式继承
// 声明父类
function SuperClass(name){
this.name = name
this.books = ['html','css','js']
}
// 父类原型共有方法
SuperClass.prototype.getName = function(){
console.log(this.name)
}
// 声明子类
function SubClass(name,time){
// 构造函数式继承父类name属性
SuperClass.call(this,name)
// 子类中新增共有属性
this.time = time;
}
// 类式继承
SubClass.prototype = new SuperClass()
// 子类原型方法
SubClass.prototype.getTime = function(){
console.log(this.time)
}
var instance1 = new SubClass('js book',2021)
instance1.books.push('node.js')
console.log(instance1.books)
instance1.getName()
instance1.getTime()
var instance2 = new SubClass('css book',2022)
instance2.books.push('h5')
console.log(instance2.books)
instance2.getName()
instance2.getTime()
分析一下instance的构造:
组合继承模式将类式继承和构造函数式继承相结合,可以看到构造函数式继承将父类的属性复制到子类(每次执行构造函数式继承,相当于重新new一个父类实例,所以不会有相互覆盖的可能),但是不涉及到protype,所以不会继承父类的原型方法。所以接下来马上使用类式继承,将父类的实例赋值给子类的prototype,因为子类原型prototype上继承父类的共有属性(比如books)和之前子类执行构造函数时生成的books属性同名,所以instance.books不会取原型链上的books属性,但是能够取到原型链上的父类原型pototype上的方法。
综上,组合继承完美结合了类式继承和构造函数式继承的优点,并且消除了他们的缺点。但是这种继承也不是完全没有缺点,就是执行了2遍的父类构造函数。
那有没有办法只执行1遍父类的构造函数,以减少消耗呢?
方法肯定是有的,那就是使用终结继承大法——寄生组合式继承!
原型式继承——封装类式继承
在说明终极继承大法之前,我们先学习一下它的组成之一,原型式继承。
function inheritObject(o){
// 声明一个过渡函数对象
function F(){}
// 过渡对象的原型继承父对象
F.prototype = o
// 返回过渡对象的一个实例,该实例的原型继承了父对象
return new F()
}
可以看出原型式继承和类式继承十分相似,实际上它就是类式继承的一个封装。只不过这里的子类是以过渡对象的形式出现的。
寄生式继承——原型式继承的二次封装
// 声明基对象
var book = {
name:'js book',
alikeBook:['css book','html book']
}
// 原型式继承
function inheritObject(o){
// 声明一个过渡函数对象
function F(){}
// 过渡对象的原型继承父对象
F.prototype = o
// 返回过渡对象的一个实例,该实例的原型继承了父对象
return new F()
}
// 寄生式继承
function createBook(obj){
// 通过原型式继承创建新对象
var o = new inheritObject(obj)
// 扩展新对象
o.getName = function(){
console.log(this.name)
}
return o
}
var newBook = new createBook(book)
寄生式继承其实就是对实例化后的原型继承对象进行扩展。
寄生组合式继承——终极
// 原型式继承
function inheritObject(o){
// 声明一个过渡函数对象
function F(){}
// 过渡对象的原型继承父对象
F.prototype = o
// 返回过渡对象的一个实例,该实例的原型继承了父对象
return new F()
}
// 寄生组合式继承
function inheritPrototype(subClass,superClass){
// 复制一份父类的原型副本保存在变量中
var p = inheritObject(superClass.prototype)
// 修正因为重写子类原型导致子类的constructor属性被修改
p.constructor = subClass
subClass.prototype = p
}
// 定义父类
function SuperClass(name){
this.name = name
this.colors = ['#000000','#ffffff','#000fff']
}
// 定义父类原型方法
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)
}
// 创建2个测试方法
var instance1 = new SubClass('js book',2021)
var instance2 = new SubClass('css book',2022)
instance1.colors.push('red')
console.log(instance1.colors)
console.log(instance2.colors)
instance2.getName()
instance2.getTime()
分析下instance1的结构:
寄生组合式继承替换掉类式继承,与构造函数式继承的结合堪称完美。首先构造函数式继承复制了父类的共有属性,然后寄生组合式继承只是将父类的原型prototype复制到了子类的prototype,并且还修正了子类constructor属性。
JavaScript不是只有一条原型链吗?怎么实现多继承呢?我们来看下下面的一段代码:
通过extend方法,我们成功将source中的属性成功复制到了target对象中,但是仅仅这样还不够,会出现几个问题:
1)如果target中有相同的属性,就会被source所覆盖
2)如果source中的某个属性是引用类型,就会导致target复制的该属性指向同一地址
3)extend方法没有涉及到prototype
解决这几个问题后,我们再考虑多继承的问题,不过是将source变成多个而已。
到这里,发现自己手写了一个深拷贝,解决了前2个问题,那prototype的问题呢?考虑到多继承和单继承不同,在此不做讨论,如果prototype也需要继承的话,可以参考extend方法。
接下来,我们需要扩展extend方法,将复制一个对象转为复制多个对象的属性。