在上一篇 《 ES6 系列 Babel 是如何编译 Class 的(上)》,咱们知道了 Babel 是如何编译 Class 的,这篇咱们学习 Babel 是如何用 ES5 实现 Class 的继承。git
function Parent (name) { this.name = name; } Parent.prototype.getName = function () { console.log(this.name) } function Child (name, age) { Parent.call(this, name); this.age = age; } Child.prototype = Object.create(Parent.prototype); var child1 = new Child('kevin', '18'); console.log(child1);
原型链示意图为:github
关于寄生组合式继承咱们在 《JavaScript深刻之继承的多种方式和优缺点》 中介绍过。express
引用《JavaScript高级程序设计》中对寄生组合式继承的夸赞就是:babel
这种方式的高效率体现它只调用了一次 Parent 构造函数,而且所以避免了在 Parent.prototype 上面建立没必要要的、多余的属性。与此同时,原型链还能保持不变;所以,还可以正常使用 instanceof 和 isPrototypeOf。开发人员广泛认为寄生组合式继承是引用类型最理想的继承范式。
Class 经过 extends 关键字实现继承,这比 ES5 的经过修改原型链实现继承,要清晰和方便不少。异步
以上 ES5 的代码对应到 ES6 就是:函数
class Parent { constructor(name) { this.name = name; } } class Child extends Parent { constructor(name, age) { super(name); // 调用父类的 constructor(name) this.age = age; } } var child1 = new Child('kevin', '18'); console.log(child1);
值得注意的是:学习
super 关键字表示父类的构造函数,至关于 ES5 的 Parent.call(this)。this
子类必须在 constructor 方法中调用 super 方法,不然新建实例时会报错。这是由于子类没有本身的 this 对象,而是继承父类的 this 对象,而后对其进行加工。若是不调用 super 方法,子类就得不到 this 对象。spa
也正是由于这个缘由,在子类的构造函数中,只有调用 super 以后,才可使用 this 关键字,不然会报错。prototype
在 ES6 中,父类的静态方法,能够被子类继承。举个例子:
class Foo { static classMethod() { return 'hello'; } } class Bar extends Foo { } Bar.classMethod(); // 'hello'
这是由于 Class 做为构造函数的语法糖,同时有 prototype 属性和 proto 属性,所以同时存在两条继承链。
(1)子类的 proto 属性,表示构造函数的继承,老是指向父类。
(2)子类 prototype 属性的 proto 属性,表示方法的继承,老是指向父类的 prototype 属性。
class Parent { } class Child extends Parent { } console.log(Child.__proto__ === Parent); // true console.log(Child.prototype.__proto__ === Parent.prototype); // true
ES6 的原型链示意图为:
咱们会发现,相比寄生组合式继承,ES6 的 class 多了一个 Object.setPrototypeOf(Child, Parent)
的步骤。
extends 关键字后面能够跟多种类型的值。
class B extends A { }
上面代码的 A,只要是一个有 prototype 属性的函数,就能被 B 继承。因为函数都有 prototype 属性(除了 Function.prototype 函数),所以 A 能够是任意函数。
除了函数以外,A 的值还能够是 null,当 extend null
的时候:
class A extends null { } console.log(A.__proto__ === Function.prototype); // true console.log(A.prototype.__proto__ === undefined); // true
那 ES6 的这段代码:
class Parent { constructor(name) { this.name = name; } } class Child extends Parent { constructor(name, age) { super(name); // 调用父类的 constructor(name) this.age = age; } } var child1 = new Child('kevin', '18'); console.log(child1);
Babel 又是如何编译的呢?咱们能够在 Babel 官网的 Try it out 中尝试:
'use strict'; function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var Parent = function Parent(name) { _classCallCheck(this, Parent); this.name = name; }; var Child = function(_Parent) { _inherits(Child, _Parent); function Child(name, age) { _classCallCheck(this, Child); // 调用父类的 constructor(name) var _this = _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).call(this, name)); _this.age = age; return _this; } return Child; }(Parent); var child1 = new Child('kevin', '18'); console.log(child1);
咱们能够看到 Babel 建立了 _inherits 函数帮助实现继承,又建立了 _possibleConstructorReturn 函数帮助肯定调用父类构造函数的返回值,咱们来细致的看一看代码。
function _inherits(subClass, superClass) { // extend 的继承目标必须是函数或者是 null if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } // 相似于 ES5 的寄生组合式继承,使用 Object.create,设置子类 prototype 属性的 __proto__ 属性指向父类的 prototype 属性 subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); // 设置子类的 __proto__ 属性指向父类 if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
关于 Object.create(),通常咱们用的时候会传入一个参数,实际上是支持传入两个参数的,第二个参数表示要添加到新建立对象的属性,注意这里是给新建立的对象即返回值添加属性,而不是在新建立对象的原型对象上添加。
举个例子:
// 建立一个以另外一个空对象为原型,且拥有一个属性 p 的对象 const o = Object.create({}, { p: { value: 42 } }); console.log(o); // {p: 42} console.log(o.p); // 42
再完整一点:
const o = Object.create({}, { p: { value: 42, enumerable: false, // 该属性不可写 writable: false, configurable: true } }); o.p = 24; console.log(o.p); // 42
那么对于这段代码:
subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } });
做用就是给 subClass.prototype 添加一个可配置可写不可枚举的 constructor 属性,该属性值为 subClass。
函数里是这样调用的:
var _this = _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).call(this, name));
咱们简化为:
var _this = _possibleConstructorReturn(this, Parent.call(this, name));
_possibleConstructorReturn
的源码为:
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
在这里咱们判断 Parent.call(this, name)
的返回值的类型,咦?这个值还能有不少类型?
对于这样一个 class:
class Parent { constructor() { this.xxx = xxx; } }
Parent.call(this, name) 的值确定是 undefined。但是若是咱们在 constructor 函数中 return 了呢?好比:
class Parent { constructor() { return { name: 'kevin' } } }
咱们能够返回各类类型的值,甚至是 null:
class Parent { constructor() { return null } }
咱们接着看这个判断:
call && (typeof call === "object" || typeof call === "function") ? call : self;
注意,这句话的意思并非判断 call 是否存在,若是存在,就执行 (typeof call === "object" || typeof call === "function") ? call : self
由于 &&
的运算符优先级高于 ? :
,因此这句话的意思应该是:
(call && (typeof call === "object" || typeof call === "function")) ? call : self;
对于 Parent.call(this) 的值,若是是 object 类型或者是 function 类型,就返回 Parent.call(this),若是是 null 或者基本类型的值或者是 undefined,都会返回 self 也就是子类的 this。
这也是为何这个函数被命名为 _possibleConstructorReturn
。
var Child = function(_Parent) { _inherits(Child, _Parent); function Child(name, age) { _classCallCheck(this, Child); // 调用父类的 constructor(name) var _this = _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).call(this, name)); _this.age = age; return _this; } return Child; }(Parent);
最后咱们整体看下如何实现继承:
首先执行 _inherits(Child, Parent)
,创建 Child 和 Parent 的原型链关系,即 Object.setPrototypeOf(Child.prototype, Parent.prototype)
和 Object.setPrototypeOf(Child, Parent)
。
而后调用 Parent.call(this, name)
,根据 Parent 构造函数的返回值类型肯定子类构造函数 this 的初始值 _this。
最终,根据子类构造函数,修改 _this 的值,而后返回该值。
ES6 系列目录地址:https://github.com/mqyqingfeng/Blog
ES6 系列预计写二十篇左右,旨在加深 ES6 部分知识点的理解,重点讲解块级做用域、标签模板、箭头函数、Symbol、Set、Map 以及 Promise 的模拟实现、模块加载方案、异步处理等内容。
若是有错误或者不严谨的地方,请务必给予指正,十分感谢。若是喜欢或者有所启发,欢迎 star,对做者也是一种鼓励。
本文为云栖社区原创内容,未经容许不得转载。