ES6 系列之 Babel 是如何编译 Class 的(下)
前言
ES5 寄生组合式继承
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);
原型链示意图为:
关于寄生组合式继承我们在 《JavaScript深入之继承的多种方式和优缺点》 中介绍过。
引用《JavaScript高级程序设计》中对寄生组合式继承的夸赞就是:
这种方式的高效率体现它只调用了一次 Parent 构造函数,并且因此避免了在 Parent.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用 instanceof 和 isPrototypeOf。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。
ES6 extend
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)。
子类必须在 constructor 方法中调用 super 方法,否则新建实例时会报错。这是因为子类没有自己的 this 对象,而是继承父类的 this 对象,然后对其进行加工。如果不调用 super 方法,子类就得不到 this 对象。
也正是因为这个原因,在子类的构造函数中,只有调用 super 之后,才可以使用 this 关键字,否则会报错。
子类的 __proto__
在 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
Babel 编译
那 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 函数帮助确定调用父类构造函数的返回值,我们来细致的看一看代码。
_inherits
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。
_possibleConstructorReturn
函数里是这样调用的:
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 系列
ES6 系列预计写二十篇左右,旨在加深 ES6 部分知识点的理解,重点讲解块级作用域、标签模板、箭头函数、Symbol、Set、Map 以及 Promise 的模拟实现、模块加载方案、异步处理等内容。
如果有错误或者不严谨的地方,请务必给予指正,十分感谢。
原文链接
本文为云栖社区原创内容,未经允许不得转载。
ES6 系列之 Babel 是如何编译 Class 的(下)的更多相关文章
- 理解Babel是如何编译JS代码的及理解抽象语法树(AST)
Babel是如何编译JS代码的及理解抽象语法树(AST) 1. Babel的作用是? 很多浏览器目前还不支持ES6的代码,但是我们可以通过Babel将ES6的代码转译成ES5代码,让所有的浏览器都 ...
- browserify babel gulp 没有编译import的文件
1.遇到坑的gulp配置: var gulp = require('gulp'), watch = require('gulp-watch'), babel = require('gulp-babel ...
- ES6学习之Babel的正确安装姿势
开始学习ES6,写点东西放上博客^_^ 本文介绍Babel6.x的安装过程~ 首先呢,可以使用Babel在线转换 https://babeljs.io/repl/ 然后进入主题:安装Babel(命令行 ...
- [js高手之路] es6系列教程 - 对象功能扩展详解
第一:字面量对象的方法,支持缩写形式 //es6之前,这么写 var User = { name : 'ghostwu', showName : function(){ return this.nam ...
- [js高手之路] es6系列教程 - 迭代器,生成器,for...of,entries,values,keys等详解
接着上文[js高手之路] es6系列教程 - 迭代器与生成器详解继续. 在es6中引入了一个新的循环结构for ....of, 主要是用来循环可迭代的对象,那么什么是可迭代的对象呢? 可迭代的对象一般 ...
- 【反编译系列】一、反编译代码(dex2jar + jd-gui)和反编译资源(apktool)
版权声明:本文为HaiyuKing原创文章,转载请注明出处! [反编译系列]二.反编译代码(jeb) [反编译系列]三.反编译神器(jadx) [反编译系列]四.反编译so文件(IDA_Pro) 概述 ...
- ES6 系列之我们来聊聊装饰器
Decorator 装饰器主要用于: 装饰类 装饰方法或属性 装饰类 @annotation class MyClass { } function annotation(target) { targe ...
- 一步步实现windows版ijkplayer系列文章之一——Windows10平台编译ffmpeg 4.0.2,生成ffplay
一步步实现windows版ijkplayer系列文章之一--Windows10平台编译ffmpeg 4.0.2,生成ffplay 一步步实现windows版ijkplayer系列文章之二--Ijkpl ...
- .Net Discovery 系列之五--深入浅出.Net实时编译机制(上)
欢迎阅读“.Net Discovery 系列”文章,本文将分上.下两部分为大家讲解.Net JIT方面的知识,敬请雅正. JIT(Just In Time简称JIT)是.Net边运行边编译的一种机制, ...
随机推荐
- bindservice与Activity通信
package com.example.jikangwang.myapplication; import android.content.ComponentName; import android.c ...
- 按模板批量修改Excel文件内容
Sub 按模板修改Excel文件() Dim MoBanWorkBook As Workbook Set MoBanWorkBook = Application.ActiveWorkbook Dim ...
- 【腾讯海纳】系统未发布时如何获取获取property_id在本地进行测试?
有现成https协议域名使用者,可忽略此文. 直接先上图,明白的人看一眼图片就知道怎么拿了,如下所示: 解释说明: 在完成添加套件,以及测试应用的前提下,按如下操作流程: 1.访问路径:登录“海纳开发 ...
- 查找datatable 中的重复记录(只查询一个字段)
StringBuilder str = new StringBuilder(); var res = new ResParameter() { code = ResponseCode.exceptio ...
- Django ORM 知识概要
相关命令 python3 manage.py makemigrations 根据模型生成相关迁移文件 python3 manage.py migrate 根据迁移文件,将表结构更新到数据库中,并在Dj ...
- mysql数据库备份_可执行文件
这段时间接手运维的工作,刚开始就尝到了数据丢失的痛!老板抱怨,同事抱怨!都说先删库再跑路,我还不想跑! 下面是我的备份记录:(分4步) 1.编写备份执行文件sqlAutoBak.sh #!/bin/s ...
- windows运维如何批量远程桌面
作用:windows下批量管理远程桌面, http://www.appmazing.com/ 官方站点 http://www.appmazing.com/files/RDO_Setup.exe wi ...
- 调研一类软件的发展演变( 1000-2000 words, in Chinese)
WARING:大量个人观点,可靠性突出一个没有. 随着时代的发展,科技的用途也在发生着改变.最初,计算机是高端科学家用来计算导弹路线.模拟核弹爆炸用的,而现在计算机更多是平凡百姓家的一台娱乐设备.当今 ...
- 1.7 All components require plug-in?
In Android, Activity, Service, ContentProvider, and BroadcastReceiver are called as four major compo ...
- idea Empty git --version output:解决
在使用idea下的git时候发现报错 但看了一下我的git-bas位置确实没有错啊,也可以启动 后来google了才下发现原来idea的这个地方不用引用的git-bash.exe的路径,而是git.e ...