解析几种继承方式
为什么需要继承?
当项目越来越大,代码越来越复杂,部分功能开始重复就得考虑代码复用,在代码复用的前提下把能抽象出来的属性和方法变成一个类,这个就是所谓的抽象类,如果需要在这个抽象类的基础上还需要进行功能拓展但是又要创建一个基于此类的抽象类就需要用到继承了。
以下继承方法都是基于ES5实现,ES6有专门的继承语法糖了暂不做讨论
常见的继承有哪几种?
- 类式继承
- 构造函数继承
- 组合继承
- ...下次补上
1.类式继承(原型链继承)
顾名思义类式继承就是直接通过类的方法来继承,我们都知道JS是一门基于原型的语言,他是没有具体类的概念的,但是我们可以通过prototype来实现关于类的种种功能。所以简而言之类式继承就是把子类的prototype链挂载到实例化的父类对象上。
function Father() {
this.name = 'dad'
this.clothColor = ['red', 'blue', 'green']
}
Father.prototype.addColor = function(color) {
this.clothColor.push(color)
}
Father.prototype.sayHi = function() {
console.log('Hi')
}
function Son() {
this.name = 'son'
this.age = 10
this.clothColor = ['white']
}
Son.prototype = new Father()
let fathers = new Father()
let sons1 = new Son()
let sons2 = new Son()
sons1.addColor('yellow')
console.log(fathers.clothColor) //\["red", "blue", "green"\]
console.log(sons1.clothColor) // \["red", "blue", "green", "yellow"\]
console.log(sons2.clothColor) // \["red", "blue", "green", "yellow"\]
console.log(sons1.sayHi()) // Hi
类式继承的优点是实现了代码的复用,每次实例化都不用再去执行类代码,所有子类得到了一样的功能,但是缺点也很明显,一个子类改变了父类引用类型数据,所有子类都会改变
2.构造函数继承
构造函数继承说的简单点就是借助父类的构造函数来实现继承
function Father() {
this.name = 'dad'
this.clothColor = ['red', 'blue', 'green']
}
Father.prototype.addColor = function(color) {
this.clothColor.push(color)
}
Father.prototype.sayHi = function() {
console.log('Hi')
}
function Son() {
Father.call(this)
// Father.prototype.constructor = Father
this.name = 'son'
this.age = 10
}
let fathers = new Father()
let sons1 = new Son()
let sons2 = new Son()
sons1.addColor('yellow') // 报错
- 构造函数继承和类式继承很相似,只不过是把Son.prototype = new Father()改成了Father.call(this),但是其实没有涉及到原型的继承,只是通过call、apply改变了父类的this指向,把this绑定到了Son类上,所以Son类无法得到父类的方法。
- 使用call、apply会导致每次实例化都会去执行父类,不符合代码复用思想,会造成大量内存堆积。
- 在每个子类实例中,每个方法都作为了实例自己的方法,当需求改变,要改动其中的一个方法时,之前所有的实例,他们的该方法都不能及时作出更新。只有后面的实例才能访问到新方法。
3. 组合继承
把组合继承放到第一第二种下面讲,就是因为组合就是原型和构造函数继承相组合来实现继承,弥补了以上两种继承的缺点
function Father() {
this.name = 'dad'
this.clothColor = ['red', 'blue', 'green']
console.log('调用次数') //4
}
Father.prototype.addColor = function(color) {
this.clothColor.push(color)
}
Father.prototype.sayHi = function() {
console.log('Hi')
}
function Son() {
Father.call(this)
this.name = 'son'
this.age = 10
}
Son.prototype = new Father()
let fathers = new Father()
let sons1 = new Son()
let sons2 = new Son()
sons1.addColor('yellow')
console.log(fathers.clothColor) //\["red", "blue", "green"]
console.log(sons1.clothColor) // \["red", "blue", "green", "yellow"\]
console.log(sons2.clothColor) // \["red", "blue", "green"\]
console.log(sons1.sayHi()) // Hi
- 组合继承结合了以上两种继承的优缺点,比较常用
- 但是组合继承会使子类拥有两套属性,一个是通过子类点直接访问,一个是在子类的原型链上,效率较低
- 每次子类实例化都会去调用父类的问题依然没得到解决,效率较低
4. 原型式继承
基于已有对象创建新对象。如果上面三种继承方式属于一类,下面讲的几种,包括原型式继承就是另一类继。原型是继承没有使用严格的构造函数,必须有一个对象可以作为另一个对象的基础,将源对象传入创建封装对象的函数,再修改目标对象
function inheritObject(obj) {
function Func() {}
Func.prototype = obj
return new Func()
}
let man = {
name: 'Norman',
clothColor: ['red', 'blue']
}
let newMan = inheritObject(man)
let secondMan = inheritObject(man)
newMan.clothColor.push('green')
console.log(newMan.clothColor) //\["red", "blue", "green"\]
console.log(secondMan.clothColor)//\["red", "blue", "green"\]
原型式继承的好处是基于已有对象创建新对象,同时还不必因此创建自定义类型,但是由于用到了原型链所以子类在修改父类引用型数据时,数据会在所有子类共享
5.寄生继承
寄生继承和原型式继承类似,都是通过创建一个用于封装继承的方法来生成对象。但是这个方法里面可以增加一些新增的方法。这里的创建对象用到了ES5的Object.create()
function inheritObject(obj) {
let clone = Object.create(obj)
clone.getName = function() { // 增加对象属性
return 'hhhh'
}
return clone
}
let man = {
name: 'Norman',
clothColor: ['red', 'blue']
}
let mans_1 = new inheritObject(man)
console.log(mans_1.name)
console.log(mans_1.getName())
缺点是每次创建一个新的对象都回去调用inheritObject方法不利于复用。
function inheritObject(obj) {
function Func() {}
Func.prototype = obj
return new Func()
}
// 等价于Object.create()
按照非ES5写法其实就是把传入的对象封装一遍实例化,再对这个实例化对象进行扩展,最后再把这个对象实例化得到目标对象。可以说是原型式继承的再一次封装。
6.寄生组合式继承
寄生组合式继承是目前来说最好的继承方式,先说组合式继承,他是比较好的继承方法,但是他会导致对象的原型链和对象属性上出现两套相同的属性,也就是父类构造函数会执行两次。寄生组合式的继承方法就是把子类对象上的属性去掉 只保留原型链上的属性。
寄生组合式继承要理解要写出来,最好先从写一个组合继承开始,再将寄生继承的概念融入
function Father() {
this.name = 'dad'
this.clothColor = ['red', 'blue']
}
Father.prototype.getWords = function() {
return 'hahahah'
}
function Mother() {
this.baby = 1
}
Mother.prototype.getBaby = function() {
return 'baby'
}
function Son() {
Father.call(this)
Mother.call(this) //多重继承 忘记写拿不到baby属性
this.age = 15
}
function inheritObject(sonClass, FatherClass, MotherClass) {
let clone
if (MotherClass) {
let minin = Object.assign(FatherClass.prototype, MotherClass.prototype)
clone = Object.create(minin) //多重继承
} else {
clone = Object.create(FatherClass.prototype) //基于父原型创建新对象 这样操作会丢失构造函数默认的constructor属性
}
clone.constructor = sonClass // 新对象的构造器和子类绑定
sonClass.prototype = clone // 把新对象赋值给继承目标子类的原型链
}
inheritObject(Son, Father, Mother)
let son \= new Son()
console.log(son.getWords())
console.log(son.getBaby())
inheritObject函数接收两个参数:子类型构造函数和超类型构造函数。
创建父类型原型的副本。为创建的副本添加constructor属性,弥补因重写原型而失去的默认的constructor属性。将新创建的对象(即副本)赋值给子类型的原型。这种方法只调用了一次父类构造函数,instanceof 和isPrototypeOf()也能正常使用。
通过对inheritObject的改动可以传入多个父类参数,Son构造函数中添加其他父类的this指向,再通过Object.assign()方法把多个父类的原型链合并为一个对象进行拷贝,就可以实现多重继承
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。