babel
Babel 是什么
Babel 是一个 JavaScript 编译器
Babel 是一个工具链,主要用于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。下面列出的是 Babel 能为你做的事情:
- 语法转换
- 通过 Polyfill 方式在目标环境中添加缺失的特性 (通过 @babel/polyfill 模块)
- 源码转换 (codemods)
Babel的功能非常纯粹,以字符串的形式将源代码传给它,它就会返回一段新的代码字符串(以及sourcemap)。他既不会运行你的代码,也不会将多个代码打包到一起,它就是个编译器,输入语言是ES6+,编译目标语言是ES5。
babel 内部使用的解析类库叫做 babylon,并非 babel 自行开发
Babel工作的三个阶段
Babel的编译过程跟绝大多数其他语言的编译器大致同理,分为三个阶段:
- 解析:将代码字符串解析成抽象语法树
- 变换:对抽象语法树进行变换操作
- 再建:根据变换后的抽象语法树再生成代码字符串
第1步转换的过程中可以验证语法的正确性,同时由字符串变为对象结构后更有利于精准地分析以及进行代码结构调整。
第2步原理就很简单了,就是遍历这个对象所描述的抽象语法树,遇到哪里需要做一下改变,就直接在对象上进行操作,比如我把IfStatement给改成WhileStatement就达到了把条件判断改成循环的效果。
第3步也简单,递归遍历这颗语法树,然后生成相应的代码。
抽象语法树是如何产生的
解析这一步又分成两个步骤:
- 分词:将整个代码字符串分割成 语法单元 数组
- 语义分析:在分词结果的基础之上分析 语法单元之间的关系
分词
首先解释一下什么是语法单元:语法单元是被解析语法当中具备实际意义的最小单元,通俗点说就是类似于自然语言中的词语。
JS代码有哪些语法单元
- 空白:JS中连续的空格、换行、缩进等这些如果不在字符串里,就没有任何实际逻辑意义,所以把连续的空白符直接组合在一起作为一个语法单元。
- 注释:行注释或块注释,虽然对于人类来说有意义,但是对于计算机来说知道这是个“注释”就行了,并不关心内容,所以直接作为一个不可再拆的语法单元
- 字符串:对于机器而言,字符串的内容只是会参与计算或展示,里面再细分的内容也是没必要分析的
- 数字:JS语言里就有16、10、8进制以及科学表达法等数字表达语法,数字也是个具备含义的最小单元
- 标识符:没有被引号扩起来的连续字符,可包含字母、_、$、及数字(数字不能作为开头)。标识符可能代表一个变量,或者true、false这种内置常量、也可能是if、return、function这种关键字,是哪种语义,分词阶段并不在乎,只要正确切分就好了。
- 运算符:+、-、*、/、>、<等等
- 括号:(…)可能表示运算优先级、也可能表示函数调用,分词阶段并不关注是哪种语义,只把“(”或“)”当做一种基本语法单元
- 还有其他:如中括号、大括号、分号、冒号、点等等不再一一列举
这拆分过程其实没啥可取巧的,就是简单粗暴地一个字符一个字符地遍历,然后分情况讨论,整个实现方法就是顺序遍历和大量的条件判断。
语义分析
语义分析就是把词汇进行立体的组合,确定有多重意义的词语最终是什么意思、多个词语之间有什么关系以及又应该再哪里断句等。
在编程语言解释当中,这就是要最终生成语法树的步骤了。不像自然语言,像“从句”这种结构往往最多只有一层,编程语言的各种从属关系更加复杂。
在编程语言的解析中有两个很相似但是又有区别的重要概念:
- 语句:语句是一个具备边界的代码区域,相邻的两个语句之间从语法上来讲互不干扰,调换顺序虽然可能会影响执行结果,但不会产生语法错误
比如return true、var a = 10、if (…) {…} - 表达式:最终有个结果的一小段代码,它的特点是可以原样嵌入到另一个表达式
比如myVar、1+1、str.replace(‘a’, ‘b’)、i < 10 && i > 0等
很多情况下一个语句可能只包含一个表达式,比如console.log(‘hi’);。estree标准当中,这种语句节点称作ExpressionStatement。
语义分析的过程又是个遍历语法单元的过程,不过相比较而言更复杂,因为分词过程中,每个语法单元都是独立平铺的,而语法分析中,语句和表达式会以树状的结构互相包含。针对这种情况我们可以用栈,也可以用递归来实现。