这里主要讲4种ES5继承以及ES6的class语法糖的继承。
这里给出父类的定义:
function SuperType(name) {
this.name = name;
}
SuperType.prototype.sayName = function () {
console.log(this.name);
};
1、原型链继承
核心:将父类的实例作为子类的原型
function SubType(name) {
this.name = name;
}
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
此时原型对象的constructor的执行变成了SuperType,因为constructor指向构造函数,所以重新指向子类的构造函数即可,即Subtype.prototype.constructor = SubType;
优点
1)非常纯粹的继承关系,实例是子类的实例,也是父类的实例,通过instanceof可以判断
2)父类新增原型方法/属性,子类都可以访问到
3)简单、易于实现
缺点
1)要想子类新增属性和方法,必须要在new SuperType()这样的语句之后执行,不能放到构造函数中
2)无法实现多继承
3)来自原型对象的引用属性是所有实例共享的
4)创建子类实例时,无法向父类构造函数传参
2、构造函数继承
核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类,在子类的构造函数内使用SuperType.call(this);
function SubType(name) {
SuperType.call(this, name);
}
优点
1、解决了原型链继承中,子类实例共享父类属性的问题
2、创建子类实例是,可以向父类传递参数
3、可以实现多继承(call多个父类对象)
缺点
1、实例并不是父类的实例,只是子类的实例(instanceof可以判断)
2、只能继承父类的实例属性和方法,不能继承原型属性/方法
3、无法实现函数服用,每个子类都有父类的实例函数的副本,影响性能
3、组合继承(构造函数继承和原型链继承的结合)
核心:在构造函数内利用call方法,直接调用属性,然后在原型prototype上绑定绑定方法(通过实例化new父对象),然后修改constructor指向子对象的构造函数。
function SubType(name){
SuperType.call(this,name);//第二次调用父类
}
SubType.prototype = new SuperType();//第一调用父类
SubType.prototype.constructor = SubType;
优点
1、既是子类的实例,也是父类的实例
2、不存在引用属性共享问题
缺点
1、调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
4、寄生组合继承(目前最优)
核心:通过寄生方式,砍掉父类的实例属性,这样在调用两次父类的构造函数的时候,就不会初始化两次实例方法/属性,避免了组合继承的缺点
function SubType(name) {
SuperType.call(this,name);
}
(function () {
var Super = function () {
//创建没有实例的方法的类
};
Super.prototype = SuperType.prototype;
SubType.prototype = new Super();//将实例作为子类的原型
SubType.prototype.constructor = SubType;
})();
5、ES6继承
核心:通过ES6语法糖class,使用extends关键字
class SubType extends SuperType{
constructor(...args){
super(...args);
}
}
通过在class的constructor方法内调用super,调用父类的constructor方法,用于新建父类的this对象。
SubType.__proto__ === SuperType;//true
SubType.prototype.__proto__ === SuperType.prototype;//true
只要带有prototype属性的函数就能被extends关键字继承
部分内容参考《JS高级程序设计第三版》和《ES6标准入门第三版》