首先我们一起来回顾一下组合继承,如果没有了解组合继承可以参考js中的继承 。
组合继承结合了原型链继承和盗用构造函数的优点,即能使得实例对象有自己的独立属性方法,还可以继承原型对象的属性和方法以便复用,但是它并不算完美,因为在组合继承中,调用了两次父类构造函数。
function SubType(name, age) {
SuperType.call(this, name) // 第二次
}
SubType.prototype = new SuperType() // 第一次
那么更完美的一种方式就是寄生式组合继承,它解决了两次调用父类构造函数的问题(这也太追求极致了吧),在介绍寄生式组合继承之前,我们先来了解下原型式继承和寄生式继承。
1. 原型式继承
原型式继承是Crockford提出的一种浅复制对象的方式,出发点在即使不自定义类型也可以通过原型实现对象之间的信息共享。
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
该函数创建了一个构造函数,这个构造函数的原型对象指向了参数对象o
,然后调用构造函数创建了对象并返回,该返回的对象的[[prototype]]
指向了对象o
/**
* 原型式继承实际上是进行对象的浅拷贝,创建一个新的对象进行返回
* 适用于对一个已有的对象,需要在此基础上创建一个新的对象(增强对象)
*/
function object(obj) {
function f() {}
f.prototype = obj
return new f()
}
let person = {
name: '小明',
friends: ['小红', '小方'],
}
let anotherPerson = object(person)
console.log(anotherPerson.__proto__ === person) // true
2. 寄生式继承
寄生式继承和原型式继承很接近,名字唬人而已,寄生式继承在原型式继承的基础上,对对象设置了方法,从而增强了对象。
/**
* 寄生继承实际上也和原型式继承差不多,通过浅拷贝对象后进行方法增强
*/
function object(original) {
function F() {}
F.prototype = original
return new F()
}
function createAnother(original) {
let clone = object(original)
clone.sayHi = function () {
console.log('hi')
}
return clone
}
let person = {
name: 'tom',
friends: ['amy', 'lucy', 'jerry'],
}
const another = createAnother(person)
another.sayHi() // hi
console.log(another.name, another.friends) // tom [ 'amy', 'lucy', 'jerry' ]
3. 寄生式组合继承
现在回来说寄生式组合继承,组合继承调用了两次父类构造函数,那么如果用一个中间变量作为父类原型的浅拷贝,来连接子类和父类不就可以了吗,这就是寄生式组合继承。
// 浅拷贝对象
function object(o) {
function F() {}
F.prototype = o
return new F()
}
// 实现寄生式组合继承
function myExtend(subType, superTtype) {
let proto = object(superTtype.prototype) // 浅拷贝父类原型对象
proto.constructor = subType // 该复制父类原型对象的≠≠构造函数指向子类
subType.prototype = proto // 子类的原型对象指向复制的父类原型对象
}
function SuperType(name) {
this.name = name
this.colors = ['red', 'blue', 'green']
}
SuperType.prototype.sayName = function () {
console.log(this.name)
}
function SubType(name, age) {
SuperType.call(this, name)
this.age = age
}
myExtend(SubType, SuperType) // 连接子类和父类
SubType.prototype.sayAge = function () {
console.log(this.age)
}
关键代码就在于myExtend(subType, superType)
函数,它实现了子类和父类的连接,浅拷贝父类的原型得到一个中间对象proto
,该对象的构造函数指向子类,子类的原型指向该对象,因而在调用结束后,中间变量销毁,由于是浅拷贝,子类和父类的联系也建立了起来,不用显示调用两次父类构造函数了。