是什么
继承(inheritance)是⾯向对象软件技术当中的⼀个概念。
如果⼀个类别B“继承⾃”另⼀个类别A,就把这个B称为“A的⼦类”,⽽把A称为“B的⽗类别”也可以称“A是B的超类”
继承的优点
继承可以使得⼦类具有⽗类别的各种属性和⽅法,⽽不需要再次编写相同的代码
在⼦类别继承⽗类别的同时,可以重新定义某些属性,并重写某些⽅法,即覆盖⽗类别的原有属性和⽅法,使其获得与⽗类别不同的功能
虽然JavaScript并不是真正的⾯向对象语⾔,但它天⽣的灵活性,使应⽤场景更加丰富
关于继承,我们举个形象的例⼦:
定义⼀个类(Class)叫汽⻋,汽⻋的属性包括颜⾊、轮胎、品牌、速度、排⽓量等
由汽⻋这个类可以派⽣出“轿⻋”和“货⻋”两个类,在汽⻋的基础属性上,为轿⻋添加⼀个后备厢、给货⻋添加⼀个⼤货箱
这样轿⻋和货⻋就是不⼀样的,但是⼆者都属于汽⻋这个类,汽⻋、轿⻋继承了汽⻋的属性,⽽不需要再次在“轿⻋”中定义汽⻋已经有的属性
在“轿⻋”继承“汽⻋”的同时,也可以重新定义汽⻋的某些属性,并重写或覆盖某些属性和⽅法,使其获得与“汽⻋”这个⽗类不同的属性和⽅法
从这个例⼦中就能详细说明汽⻋、轿⻋以及卡⻋之间的继承关系
实现⽅式
下⾯给出JavaScripy常⻅的继承⽅式:
● 原型链继承
● 构造函数继承(借助 call)
● 组合继承
● 原型式继承
● 寄⽣式继承
● 寄⽣组合式继承
原型链继承
原型链继承是⽐较常⻅的继承⽅式之⼀,其中涉及的构造函数、原型和实例,三者之间存在着⼀定的关系,即每⼀个构造函数都有⼀个原型对象,原型对象⼜包含⼀个指向构造函数的指针,⽽实例则包含⼀个原型对象的指针
举个例⼦
上⾯代码看似没问题,实际存在潜在问题
改变s1的play属性,会发现s2也跟着发⽣变化了,这是因为两个实例使⽤的是同⼀个原型对象,内存
空间是共享的
构造函数继承
借助 call调⽤Parent函数
可以看到,⽗类原型对象中⼀旦存在⽗类之前⾃⼰定义的⽅法,那么⼦类将⽆法继承这些⽅法
相⽐第⼀种原型链继承⽅式,⽗类的引⽤属性不会被共享,优化了第⼀种继承⽅式的弊端,但是只能继承⽗类的实例属性和⽅法,不能继承原型属性或者⽅法
组合继承
前⾯我们讲到两种继承⽅式,各有优缺点。组合继承则将前两种⽅式继承起来
这种⽅式看起来就没什么问题,⽅式⼀和⽅式⼆的问题都解决了,但是从上⾯代码我们也可以看到
Parent3 执⾏了两次,造成了多构造⼀次的性能开销
原型式继承
这⾥主要借助Object.create⽅法实现普通对象的继承
同样举个例⼦
这种继承⽅式的缺点也很明显,因为Object.create⽅法实现的是浅拷⻉,多个实例的引⽤类型属性指向相同的内存,存在篡改的可能
寄⽣式继承
寄⽣式继承在上⾯继承基础上进⾏优化,利⽤这个浅拷⻉的能⼒再进⾏增强,添加⼀些⽅法
其优缺点也很明显,跟上⾯讲的原型式继承⼀样
寄⽣组合式继承
寄⽣组合式继承,借助解决普通对象的继承问题的Object.create ⽅法,在前⾯⼏种继承⽅式的优缺点
基础上进⾏改造,这也是所有继承⽅式⾥⾯相对最优的继承⽅式
可以看到 person6 打印出来的结果,属性都得到了继承,⽅法也没问题
⽂章⼀开头,我们是使⽤ES6 中的extends关键字直接实现 JavaScript的继承
利⽤babel⼯具进⾏转换,我们会发现extends实际采⽤的也是寄⽣组合继承⽅式,因此也证明了这种⽅式是较优的解决继承的⽅式
总结
下⾯以⼀张图作为总结:

通过Object.create 来划分不同的继承⽅式,最后的寄⽣式组合继承⽅式是通过组合继承改造之后的最优继承⽅式,⽽ extends 的语法糖和寄⽣组合继承的⽅式基本类似