组合式继承是最成熟的继承方法, 也是现在最常用的继承方法,众多JS库采用的继承方案也是它。
寄生组合式继承相对于组合继承有如下优点:
- 只调用一次父类
fatherFn
构造函数。
- 避免在子类prototype上创建不必要多余的属性。
使用原型式继承父类的prototype,保持了原型链上下文不变。
子类的prototype只有子类通过prototype声明的属性/方法和父类prototype上的属性/方法泾渭分明。
ES6 extends继承:
ES6继承的原理跟寄生组合式继承是一样的。
ES6 extends
核心代码:
这段代码是通过babel在线编译成es5, 用于子类prototype原型式继承父类prototype
的属性/方法。
// 寄生式继承 封装继承过程
function _inherits(son, father) {
// 原型式继承: 设置father.prototype为son.prototype的原型 用于继承father.prototype的属性/方法
son.prototype = Object.create(father && father.prototype);
son.prototype.constructor = son; // 修正constructor 指向
// 将父类设置为子类的原型 用于继承父类的静态属性/方法(father.some)
if (father) {
Object.setPrototypeOf
? Object.setPrototypeOf(son, father)
: son.__proto__ = father;
}
}
另外子类是通过借用构造函数继承(call
)来继承父类通过this
声明的属性/方法,也跟寄生组合式继承一样。
ES5继承与ES6继承的区别:
本段摘自阮一峰-es6入门文档
扩展:
为什么要修正construct指向?
在寄生组合式继承中有一段如下一段修正constructor 指向的代码,很多人对于它的作用以及为什么要修正它不太清楚。
son.prototype.constructor = son; // 修正constructor 指向
construct的作用
MDN的定义:返回创建实例对象的Object
构造函数的引用。
即返回实例对象的构造函数的引用,例如:
let instance = new sonFn()
instance.constructor // sonFn函数
construct
的应用场景:
当我们只有实例对象没有构造函数的引用时:
某些场景下,我们对实例对象经过多轮导入导出,我们不知道实例是从哪个函数中构造出来或者追踪实例的构造函数,较为艰难。
这个时候就可以通过实例对象的constructor
属性来得到构造函数的引用:
let instance = new sonFn() // 实例化子类
export instance;
// 多轮导入+导出,导致sonFn追踪非常麻烦,或者不想在文件中再引入sonFn
let fn = instance.construct
// do something: new fn() / fn.prototype / fn.length / fn.arguments等等
保持construct
指向的一致性:
因此每次重写函数的prototype都应该修正一下construct
的指向,以保持读取construct
行为的一致性。
小结
继承也是前端的高频面试题,了解本文中继承方法的优缺点,有助于更深刻的理解JS继承机制。除了组合继承和寄生式继承都是由其他方法组合而成的,分块理解会对它们理解的更深刻。
建议多看几遍本文,建个html
文件试试文中的例子,两相结合更佳!
对prototype还不是很理解的同学,可以再看看:JS基础-函数、对象和原型、原型链的关系
觉得我的博客对你有帮助的话,就给我点个Star吧!
前端进阶积累、公众号、GitHub、wx:OBkoro1、邮箱:obkoro1@foxmail.com
以上2019/9/22
作者:OBKoro1
参考资料:
JS高级程序设计(红宝书)6.3继承
java script常用八种继承方案