Babel的初步认识

Babel是前端很常用的转码器,更准确地说是转译器,是从源码到源码的转换编译器,例如可以将我们按照ES6标准写的代码转为ES5标准,也就是说可以直接使用ES6的最新标准来编写脚本,而不用担心现有环境是否支持此标准。

例如:Babel可以将我们最常用的箭头函数:

const demo = item => item + 1;

转译成ES5的函数写法:

const demo = function(item){
    return item + 1;
}

将ES6标准转译成ES5,不用担心各大浏览器是否已经支持ES6的最新标准这个确实是解决了我们工作中一大问题,但是Babel的功能并不止于此,它是可以转译很多种语法的,例如我们最常用的react中JSX的语法,而且Babel的核心就是利用插件,通过不同的插件可以转译不同的语法,让我们可以畅快的去尝试最新的语法。

Babel的三大主要步骤

我们先来看一张图,这是我网上找来的,我感觉这张图已经把Babel的工作步骤画的很清楚了。


Babel流程图

从上图可以看出Babel的三大步骤分别是:解析(parse),转换(transform),生成(generate)。嘿嘿,我发现图中的英文有点问题,大家可以查一下是不是,如果是我翻译错了请指正。

什么是AST?
在详细解释这三大步骤前,我们有必要先来了解一下什么是AST?
“ AST ”其实叫做“ 抽象语法树 ”,是源代码的抽象语法结构的树状表现形式,其实个人觉得babel对于AST和我们熟悉的jquery对于DOM有点像。我们可以想象一下如何将JS代码用树状表示出来。

var a = 1 + 1
var b = 2 + 2

上面声明了两个变量,如何用树状表示他们呢?首先一定会有东西可以代表这些声明、变量名、常量等等的信息。很明显,这棵树上有两个变量,两个变量名a和b,有两个运算语句,操作符都是+号。但是有了这些还不够,既然是树,树枝连树枝,还必须建立起彼此之间的关系,比如一个声明语句,声明类型是var,左侧是变量名,右侧是表达式,有了这些信息我们就可以还原这个程序了。这个就是把源码解析成AST时所做的事情了。

在AST中我们用node(节点)来表示每个代码片段,比如上面程序的整体就是一个节点(Program,所有的AST根节点都是Program节点),然后下面有两条语句,所以它的body属性上就两个声明节点VariableDeclaration。所以上面程序的AST类似这样:

AST结构图

从图上可以看出节点上用了各个属性来表示各种信息以及程序之间的关系。

解析(parse):

在大概了解了AST是个啥东西后,我们可以来了解三大步骤了,首先是第一步解析。主要是为了接收代码并输出AST,也就是将代码变为树状,这个步骤又分两个阶段:词法分析(Lexical Analysis)和 语法分析(Syntactic Analysis)。

词法分析
词法分析阶段是把字符串形式的代码转换成令牌(tokens)流。你可以把tokens看成是一个语法片段数组。例如:n*n代码经过词法分析阶段后转换成了tokens:

// n*n
[
  { type: { ... }, value: "n", start: 0, end: 1, loc: { ... } },
  { type: { ... }, value: "*", start: 2, end: 3, loc: { ... } },
  { type: { ... }, value: "n", start: 4, end: 5, loc: { ... } },
  ...
]

每一个type又有一组属性来描述该令牌:

{
  type: {
    label: 'name',
    keyword: undefined,
    beforeExpr: false,
    startsExpr: true,
    rightAssociative: false,
    isLoop: false,
    isAssign: false,
    prefix: false,
    postfix: false,
    binop: null,
    updateContext: null
  },
  ...
}

语法分析
语法分析阶段是把一个令牌(tokens)流转换成AST的形式,以便于后续的操作。

转换(transform)

第二步是转换,主要是用来接收解析好的AST并对其进行遍历,在此过程中对节点进行添加、更新或者移除等操作,准确的说是利用我们配置好的plugins/presets把Parser生成的AST转变为新的AST,这一步是插件要介入工作的部分,从三大步骤的图中也可以看出占了很大一块的比重,足以看出这个转换过程就是Babel中最复杂的部分,我们平时配置的plugins/presets就是作用在这里了。

生成(generate)

第三步是生成,最后一步把最后经过一系列转换后的最新AST转换成字符串形式的代码,同时还会创建源码映射(source maps)。代码生成反而比较简单,只要深度优先遍历整个AST,然后构建可以表示转换后代码的字符串就可以了。

Visitors(访问者)

当我们说到“进入”一个节点时,其实是在说我们在访问它,之所以使用这样的术语是因为有一个访问者模式的概念。
这里的访问者是一个用于AST遍历的跨语言的模式。简单来说就是一个对象,定义了用于在一个树状结构中获取具体节点的方法,例如:

const MyVisitor = {
  Identifier: {
    // 当进入Identifier节点的时候执行
    enter() { 
      console.log("Entered");
    },
    // 当退出Identifier节点的时候执行
    exit() {
      console.log("Exited!");
    }

  }
};

每一个节点都会有自己对应的type,比如变量节点Identifier等。上例中我们给babel提供了一个MyVisitor对象,在这个对象上面我们以这些节点的type做为key,已一个函数作为值,这样在遍历进入到对应节点时,babel就会执行对应的enter函数,向上遍历退出对应节点时,babel就会去执行对应的exit函数。

Paths(路径)
我们通过visitor可以在遍历到对应节点执行对应的函数,可是要修改对应节点的信息,还是不够,毕竟要增删节点,我们不能等进入节点了才执行,我们还需要拿到对应节点的信息以及节点和所在的位置(即和其他节点间的关系), visitor在遍历到对应节点执行对应函数时候会给我们传入path参数,辅助我们完成上面这些操作。Path 是表示两个节点之间连接的对象,而不是当前节点,我们上面访问到了Identifier节点,它传入的 path参数看起来是这样的:

{
  "parent": {
    "type": "VariableDeclarator",
    "id": {
      ...
    },
    ....
  },
  "node": {
    "type": "Identifier",
    "name": "..."
  }

从上例可以看出:path.node.name可以获得当前节点的name,path.parent.id可以获得父节点的id,另外path对象上面还包含了添加、更新、移动和删除节点有关的很多方法,至于这些有关的方法就不再这里展开了,可以看文档解决。上面说visitor在遍历到对应节点执行对应函数时候会给我们传入path参数,所以我们可以根据这个修改一下上文中的MyVisitor函数:

const MyVisitor = {
  Identifier: {
    // 当进入Identifier节点的时候执行
    enter(path) { 
      console.log('traverse enter a Identifier node the name is ' + path.node.name);
    },
    // 当退出Identifier节点的时候执行
    exit(path) {
      console.log('traverse exit a Identifier node the name is ' + path.node.name);
    }
  }
};

这样我们就可以操作想要改变的节点了,嗯嗯~~ very good!!

总结

最后我们总结一下,Babel最重要的就是熟悉它的工作步骤,也就是它的原理:

  1. 接收源代码
  2. 将源代码转成字符串形式
  3. 把字符串形式的源代码转换成令牌流
  4. 把一个令牌流转换成AST的树状形式。
  5. 接收AST并对其进行遍历,在此过程中可以对节点进行各 种操作,比如添加、更新、移除等等。
  6. 深度遍历最终的AST树,然后构建可以表示转换后代码的字符串,并且同时创建源代码映射。

其实很多猿兄都和我一样,刚接触babel的时候,直接上手用,看着文档知道如何用,但是不知背后的原理,今天这一片笔记也是看了好几篇文档和大牛的博客整理出来的比较关键的几点,看上去简简单单的转译器,其实背后的实现还是挺不容易的,我们已经简单的分析了代码,并且可以修改一些抽象语法树上的内容来达到我们的目的,不过开头的时候也说了对于Babel而言插件是很重要的,现阶段Babel已经不仅仅是去转换ES6了,最常用的还有转换react中JSX的语法,所以除了懂得原理以外,我们也可以自己实际去编写一些有意思的插件来应用与自己的工作中,更好的提高对Babel的理解,今天介绍就到这里,还是那句话如有总结不到位的,希望各位猿兄指教。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,064评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,606评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,011评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,550评论 1 269
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,465评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,919评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,428评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,075评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,208评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,185评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,191评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,914评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,482评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,585评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,825评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,194评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,703评论 2 339

推荐阅读更多精彩内容