本篇文章中,主要探究的是有两个方面,一个是ES6之前类的继承方式,一个是ES6类继承与非ES6的区别
ES6之前类的继承方式概览:
- 类继承
- 构造函数式继承
- 组合式继承
- 原型式继承
- 寄生式继承
- 寄生组合式继承
类继承
本质:将子类原型修改为父类实例
// 父类
function Teacher(name) {
this.name = name;
this.students = ['a', 'b', 'c']
}
Teacher.prototype.teach = function() {
console.info(this.name + ' teach students')
}
// 子类
function MathTeacher(name) {
this.name = name;
}
// 子类的原型,指向父类的实例
MathTeacher.prototype = new Teacher();
// 校验
const teacher = new MathTeacher('john');
const teacher2 = new MathTeacher('lily');
console.info(teacher instanceof Teacher); // true
// 可访问父类属性和方法
teacher.teach() //john teach students
console.info(teacher.students) // ['a', 'b', 'c']
缺点:
- 修改来自原型的引用类型属性时,会有副作用:影响所有实例
- 无法传递参数:不能向父类传递构造函数参数
teacher.students.push('d');
// teacher.students ['a', 'b', 'c', 'd']
// teacher1.students ['a', 'b', 'c', 'd']
构造函数式继承
本质:在子类构造函数里使用call/apply
来实现继承。盗用继承这个名称可能更贴合
// 父类
function Teacher(name) {
this.name = name;
this.students = ['a', 'b', 'c']
}
Teacher.prototype.teach = function() {
console.info(this.name + ' teach students')
}
// 子类
function MathTeacher(name) {
Teacher.call(this, name);
}
// 校验
const teacher = new MathTeacher('john');
console.info(teacher instanceof Teacher); // false
teacher.teach(); // throw error -> Uncaught TypeError: teacher.teach is not a function
缺点:
- 无法通过instanceOf校验
- 无法调用父类方法及属性:即没有继承到
组合式继承
本质:组合以上两种继承方式(类式继承&构造函数式继承)
// 父类
function Teacher(name) {
this.name = name;
this.students = ['a', 'b', 'c']
}
Teacher.prototype.teach = function() {
console.info(this.name + ' teach students')
}
// 子类
function MathTeacher(name) {
Teacher.call(this, name);
}
// !!diff
MathTeacher.prototype = new Teacher();
// 校验
const teacher = new MathTeacher('john');
const teacher1 = new MathTeacher('john');
console.info(teacher instanceof Teacher); // true
teacher.teach(); // john teach students
teacher.students.push('d');
组合继承基本解决了最开始两种继承方式的缺点,但是又产生了新的问题:父类构造函数被调用了两次
原型式继承
本质上是对类继承的一种封装,可等价于Object.create
,核心就是借助原型基于已有的对象创建一个新的对象,同时还不必因此创建自定义类型
function createObj(o) {
function OO() {}
OO.prototype = o;
return new OO()
}
缺陷:同 类继承
寄生式继承
在原型式继承的基础上进行拓展,本质是依托一个内部对象来生成一个新对象,因此称为寄生
// 父类
function Teacher(name) {
this.name = name;
this.students = ['a', 'b', 'c']
}
Teacher.prototype.teach = function() {
console.info(this.name + ' teach students')
}
const teacher = new Teacher('john');
// 子类
function MathTeacher(obj) {
// 继承obj的原型
const o = Object.create(obj);
return o;
}
// 实例化子类,需传入父类
const mathTeacher = new MathTeacher(teacher);
缺陷:修改原型上的引用类型,会同步所有的子类型实例
寄生组合式继承
结合寄生继承与组合式继承,基本解决以上方式的问题
// 核心继承方法
function inherit(child, parent) {
// 继承父类型的原型
const o = Object.create(parent.prototype);
// 重写子类原型
child.prototype = o;
// 重写子类被污染的constructor
o.constructor = child;
}
// 父类
function Teacher(name) {
this.name = name;
this.students = ['a', 'b', 'c']
}
Teacher.prototype.teach = function() {
console.info(this.name + ' teach students')
}
const teacher = new Teacher('john');
// 子类
function MathTeacher(name) {
// 继承父类属性
Teacher.call(this, name);
}
// 继承
inherit(MathTeacher, Teacher);
MathTeacher.prototype.calc = function() {
console.info(this.name + ' teach students to calc')
}
const mathTeacher = new MathTeacher('john');
const mathTeacher2 = new MathTeacher('lily');
mathTeacher.teach() // john teach students
mathTeacher.students.push('d')
console.info(mathTeacher.students, mathTeacher2.students)// 不互相影响
最终达成的效果:
- 子类继承父类的属性和方法,同时,属性也没有被创建在原型链上,因此,多个子类不会共享同一个属性
- 子类可以传递动态参数给父类
- 父类的构造函数只执行了一次
缺陷:子类的方法必须要在继承后编写,否则继承时会被覆盖掉。什么意思呢?接上面的代码举个例子:
// 父类
function Parent() {/* ... */}
// 子类
function Child() {/* ... */}
// 定义子类方法
Child.prototype.fn = function() {/* ... */}
// 继承
inherit(Child, Parent);
// 实例化子类
const child = new Child();
child.fn(); // throw error -> Uncaught TypeError: child.fn is not a function
可以看到在上面的例子中,定义子类的方法发生在继承前,继承时方法被覆盖了,导致抛出异常。
改进:针对该缺陷,可以改进继承方法
// 核心继承方法
function inherit(child, parent) {
// 继承父类型的原型
const o = Object.create(parent.prototype);
// 重写子类原型 !!diff
child.prototype = Object.assign(o, child.prototype)
// 重写子类被污染的constructor
o.constructor = child;
}
ES6 Class 继承
ES6提供了很方便的定义类的语法糖class
,使开发者可以很快速直观的创建一个类,而不用再去写一堆具有混淆性的function了。ES6的类也提供了很方便的继承方法,那就是关键字extends
,使用如下
class Teacher {
constructor(name) {
this.name = name;
}
teach() {
console.info(this.name + ' teaches students')
}
}
class MathTeacher extends Teacher {
calc() {
console.info(this.name + ' teaches students to calc')
}
}
既然是语法糖,就有不用class实现类的形式,那么究竟是如何实现呢?我们可以用babel来翻译一下以上例子:
"use strict";
// 继承方法
function _inheritsLoose(subClass, superClass) {
subClass.prototype = Object.create(superClass.prototype);
subClass.prototype.constructor = subClass;
_setPrototypeOf(subClass, superClass);
}
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
// 父类
var Teacher = /*#__PURE__*/function () {
function Teacher(name) {
this.name = name;
}
var _proto = Teacher.prototype;
_proto.teach = function teach() {
console.info(this.name + ' teaches students');
};
return Teacher;
}();
// 子类
var MathTeacher = /*#__PURE__*/function (_Teacher) {
_inheritsLoose(MathTeacher, _Teacher);
function MathTeacher() {
return _Teacher.apply(this, arguments) || this;
}
var _proto2 = MathTeacher.prototype;
_proto2.calc = function calc() {
console.info(this.name + ' teaches students to calc');
};
return MathTeacher;
}(Teacher);
看起来是不是很熟悉,没错,就是寄生组合式继承
文章基于此文章进行分析
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!