TAT.heyli babel到底将代码转换成什么鸟样?
In 未分类 on 2016年05月15日 by view: 3,698
4


原文链接

前言

将babel捧作前端一个划时代的工具一定也不为过,它的出现让许多程序员幸福地用上了es6新语法。但你就这么放心地让babel跑在外网?反正我是不放心,我就曾经过被坑过,于是萌生了研究babel代码转换的想法。本文不是分析babel源码,仅仅是看看babel转换的最终产物。

es6在babel中又称为es2015。由于es2015语法众多,本文仅挑选了较为常用的一些语法点,而且主要是分析babel-preset-2015这个插件(react开发的时候,常在webpack中用到这个preset)。

babel-preset-2015

打开babel-preset2015插件一看,一共20个插件。熟悉es2015语法的同志一看,多多少少能从字面意思知道某个插件是用于哪种语法的转换

  • babel-plugin-transform-es2015-template-literals => es2015模板

  • babel-plugin-transform-es2015-literals

  • babel-plugin-transform-es2015-function-name => 函数name属性

  • babel-plugin-transform-es2015-arrow-functions => 箭头函数

  • babel-plugin-transform-es2015-block-scoped-functions => 函数块级作用域

  • babel-plugin-transform-es2015-classes => class类

  • babel-plugin-transform-es2015-object-super => super提供了调用prototype的方式

  • babel-plugin-transform-es2015-shorthand-properties => 对象属性的快捷定义,如obj = { x, y }

  • babel-plugin-transform-es2015-computed-properties => 对象中括号属性,如obj = {[‘x]: 1}

  • babel-plugin-transform-es2015-for-of => 对象for of遍历

  • babel-plugin-transform-es2015-sticky-regex

  • babel-plugin-transform-es2015-unicode-regex

  • babel-plugin-check-es2015-constants => const常量

  • babel-plugin-transform-es2015-spread => 对象扩展运算符属性,如…foobar

  • babel-plugin-transform-es2015-parameters => 函数参数默认值及扩展运算符

  • babel-plugin-transform-es2015-destructuring => 赋值解构

  • babel-plugin-transform-es2015-block-scoping => let和const块级作用域

  • babel-plugin-transform-es2015-typeof-symbol => symbol特性

  • babel-plugin-transform-es2015-modules-commonjs => commonjs模块加载

  • babel-plugin-transform-regenerator => generator特性

var, const and let

const和let现在一律转换成var。那const到底如何保证不变呢?如果你在源码中第二次修改const常量的值,babel编译会直接报错。
转换前

转换后:

那let的块级作用怎么体现呢?来看看下面例子,实质就是在块级作用改变一下变量名,使之与外层不同。
转换前:

转换后:

赋值解构

写react的时候,我们使用负值解构去取对象的值,用起来非常爽,像这样:

我们来看看转换的结果:

至于数组呢?如果是一个匿名数组,则babel会帮你先定义一个变量存放这个数组,然后再对需要赋值的变量进行赋值。
转换前:

转换后:

看到这个,感觉转换结果跟我们想的还蛮一致。哈哈,使用的噩梦还没开始。

如果使用匿名对象直接进行赋值解构会怎样呢?如下。babel为了使接收的变量唯一,直接就将匿名对象里的属性拼在一起,组成接收这个匿名对象的变量,吓得我赶紧检查一下项目里有没有这种写法。
转换前:

转换后:

还有一种对象深层次的解构赋值:
转换前:

转换后:

babel在代码顶部生产了一个公共的代码_slicedToArray。大概就是将对象里面的一些属性转换成数组,方便解构赋值的进行。但Symbol.iterator的兼容性并不好(如下图),还是谨慎使用为妙。
3

另外,下面这种对字符串进行赋值解构也同样使用到_slicedToArray方法:

函数参数默认值及扩展运算符

在es5的年代,一般我们写参数的默认值都会这么写:

我们来看看babel的转换办法:

babel这里使有了arguments来做判。第一种情况涉及解构赋值,因此x和y的值还是有可能是undefined的。至于第二种情况,则会保证2个参数的默认值分别是1和2.

再来看一种。…y代表它接收了剩下的参数。也就是arguments除了第一个标号的参数之外剩余的参数。
转换前:

转换后:

箭头函数

剪头函数其实主要是省了写函数的代码,同时能够直接用使外层的this而不用担心context切换的问题。以前我们一般都要在外层多写一个_this/self直向this。babel的转换办法其实跟我们的处理无异。
转换前:

转换后:

对象的能力增强

对象属性的快捷定义

转换前:

转换后:

对象中括号属性

es2015开始新增了在对象中用中括号解释属性的功能,这对变量、常量等当对象属性尤其有用。
转换前:

转换后:

看似简单的属性,babel却大动干戈。新增了一个_defineProperty函数,给新建的_obj = {}进行属性定义。除此之外使用小括号包住一系列从左到右的运算使整个定义更简洁。

使用super去调用prototype

以前我们一般都用obj.prototype或者尝试用this去往上寻找prototype上面的方法。而babel则自己写了一套在prototype链上寻找方法/属性的算法。
转换前

转换后:

Object.assign 和 Object.is

es6新增的Object.assign极大方便了对象的克隆复制。但babel的es2015 preset并不支持,所以没对其进入转换,这会使得一些移动端机子遇到这种写法会报错。所以一般开发者都会使用object-assign这个npm的库做兼容。

Object.is用于比较对象的值与类型,es2015 preset同样不支持编译。

es6模板

多行字符串

转换前:

转换后:

字符中变量运算

转换前:

转换后:

标签模板

es6的这种新特性给模板处理赋予更强大的功能,一改以往对模板进行各种replace的处理办法,用一个统一的handler去处理。babel的转换主要是添加了2个属性,因此看起来也并不算比较工程浩大的编译。
转换前:

转换后:

模块化与类

类class

javascript实现oo一直是非常热门的话题。从最原始时代需要手动维护在构造函数里调用父类构造函数,到后来封装好函数进行extend继承,再到babel出现之后可以像其它面向对象的语言一样直接写class。es2015的类方案仍然算是过渡方案,它所支持的特性仍然没有涵盖类的所有特性。目前主要支持的有:

  • constructor

  • static方法

  • get 方法

  • set 方法

  • 类继承

  • super调用父类方法。

转换前:

转换后(由于代码太长,先省略辅助的方法):

先前在用react重构项目的时候,所有的react组件都已经摒弃了es5的写法,一律采用了es6。用类的好处写继续更加方便,但无法用mixin,需要借助更新的es7语法中的decorator才能够实现类mixin的功能(例如pureRender)。但这次分析完babel源码之后,才发现原来babel在实现class特性的时候,定义了许多方法,尽管看起来并不太优雅。

模块化

在开发react的时候,我们往往用webpack搭配babel的es2015和react两个preset进行构建。之前看了一篇文章对babel此处的模块加载有些启发(《分析 Babel 转换 ES6 module 的原理》)。

示例:

通过webpack与babel编译后:

es6的模块加载是属于多对象多加载,而commonjs则属于单对象单加载。babel需要做一些手脚才能将es6的模块写法写成commonjs的写法。主要是通过定义__esModule这个属性来判断这个模块是否经过babel的编译。然后通过_interopRequireWildcard对各个模块的引用进行相应的处理。

另一个发现是,通过webpack打包babel编译后的代码,每一个模块里面都包含了相同的类继承帮助方法,这是开发时忽略的。由此可看,在开发react的时候用es5的语法可能会比使用es6的class能使js bundle更小。

babel es2015 loose mode

开发家校群的时候,在android4.0下面报esModule错误的问题,如下:
Uncaught TypeError: Cannot assign to read only property '__esModule' of #<Object>。

经查证,发现是构建中babel-es2015 loader的模式问题,会导致Android4.0的用户有报错。只需要使用loose mode就可以解决问题。下面是相关的stackoverflow issue以及对应解决问题的npm包。

那么es2015和normal mode和loose mode有什么区别呢,这个出名的博客略有介绍:Babel 6: loose mode

实质就是(作者总结)normal mode的转换更贴近es6的写法,许多的property都是通过Object.defineProperty进行的。而loose mode则更贴近es5的写法,性能更好一些,兼容性更好一些,但将这部份代码再转换成native es6的话会比较麻烦一些(感觉这一点并不是缺点,有源码就可以了)。

上面esModule解决的办法,实质就是将

改成 
exports.__esModule = true;

再举个例子,如下面的Cat类定义:

正常模式会编译为:

loose mode模式会编译为:

babel es2015中loose模式主要是针对下面几个plugin:

  • transform-es2015-template-literals

  • transform-es2015-classes

  • transform-es2015-computed-properties

  • transform-es2015-for-of

  • transform-es2015-spread

  • transform-es2015-destructuring

  • transform-es2015-modules-commonjs

每一种的转换方式在此就不再赘述了,大家可以回家自己试。

如有错误,恳请斧正!

参考文章:

babel try out

template literals

block-scoped functions

classes

objects

commonjs and es6 module

原创文章转载请注明:

转载自AlloyTeam:http://www.alloyteam.com/2016/05/babel-code-into-a-bird-like/

  1. paranoidjk 2016 年 7 月 6 日

  2. zhang 2016 年 5 月 25 日

  3. evencom 2016 年 5 月 23 日

  4. 硬货 2016 年 5 月 23 日

    硬货

发表评论