- Babel
- Babel 是什么
- AST 是什么
- 访问者模式
- charCodeAt
- 其他内容简单介绍
- cli
- plugin
- presets
- polyfill
- babel-runtime & babel-plugin-transform-runtime
- .babelrc、babel.config.js、package.json 等配置
- 插件
- @babel/parser 7.x(babylon 6.x)
- @babel/traverse 7.x(babel-traverse 6.x)
- @babel/generator 7.x (babel-generator 6.x)
- @babel/types
- 过程
- 解析
- 词法分析
- 语法分析
- 转换
- 访问者模式
- 生成
- 解析
- 补充
- String.prototype.codePointAt 与 String.prototype.charCodeAt
Babel
Babel 是什么
Babel 是 JavaScript 编译器 , 简单来说是把 JavaScript 中 es6/7/8 之类的的新语法转化为 es5,让低端运行环境(如浏览器和 node )能够认识并执行。babel 也可以转化为更低的规范。但以目前情况来说,es5 规范已经足以覆盖绝大部分浏览器,因此常规来说转到 es5 是一个安全且流行的做法。
抽象语法树:
源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。之所以说语法是“抽象”的,是因为这里的语法并不会表示出真实语法中出现的每个细节。
简单来说就是对象描述语法结构。
应用场景:
- eslint 对代码错误或风格的检查,发现一些潜在的错误
- IDE 的错误提示、格式化、高亮、自动补全等
- UglifyJS 压缩代码
- 代码打包工具 webpack,从入口模块解析,寻找依赖模块
访问者模式
访问者模式:封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。
举个例子:我去朋友家做客,那么朋友属于主人,我则属于访问者。这时刚好朋友在炒菜,却没得酱油了。如果朋友下去买酱油将会很麻烦而且会影响炒菜。这时就到我这个访问者出马了。一溜烟的出去打着酱油就回来了。简单理解的来说就是,访问者在主人原来的基础上帮助主人去完成主人不方便或者完不成的东西。
1 | var Visitor = {} // 访问者对象 |
push 方法将值追加到数组中,
push
方法具有通用性。该方法和call()
或apply()
一起使用时,可应用在类似数组的对象上。push
方法根据length
属性来决定从哪里开始插入给定的值。如果length
不能被转成一个数值,则插入的元素索引为 0,包括length
不存在时。当length
不存在时,将会创建它。
1 | var obj = { length: 2 } |
String.prototype.charCodeAt:返回字符所代表的 unicode 编码,返回值是 0~65535 之间的整数
1 | 'a'.charCodeAt(0) //97 |
其他内容简单介绍
命令行工具,可通过命令行编译文件。
1 | 安装 |
Babel 作为 JavaScript 编译器,有三个过程:解析、转换、生成。如果什么都不做。它基本上只是将代码解析之后再输出同样的代码。如果想要 Babel 做一些实际的工作,就需要为其添加插件。
插件的作用:用于转换代码
babel-plugin-transform-remove-console,用于移除代码中的 console,上生产环境其实这些都是不需要的。可以减少包体积。
presets 可以作为 Babel 插件的套餐。
预设的作用:不用自己组合插件
@babel/preset-env:这个插件整合了很多插件(比如要转义箭头函数,需要使用到 @babel/plugin-transform-arrow-functions
这个插件,转义 class
需要使用 @babel/plugin-transform-classes
)。但是一些 API 没有转换( Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise),导致一些新的 API 老版浏览器不兼容。所以这个时候就需要一些工具来为浏览器做这个兼容。
可以使用新的 API(例如 Promise 或 WeakMap),静态方法(例如 Array.from 或 Object.assign),实例方法(例如 Array.prototype.includes)等。为了做到这一点,polyfill 把这些添加到了全局环境上。在 CommonJS 或者 ES module 环境下使用,都需要在入口顶部引入它。
缺点:
- babel-polyfill 会污染全局变量,给很多类的原型链上都作了修改,这就有不可控的因素存在。
- 使用 babel-polyfill 会导致打出来的包非常大,很多其实没有用到,对资源来说是一种浪费。
因为上面的问题,所以在 Babel7 中增加了 babel-preset-env,我们设置 "useBuiltIns": "usage"
这个参数值就可以实现按需加载 babel-polyfill (也就是有使用到的 API 才引入 polyfill 版本)
Babel 7.4.0 开始不推荐使用这个软件包
@babel-runtime & babel-plugin-transform-runtime
因为 babel-polyfill 会污染全局作用域,还有 Array、String 等的静态与实例属性。
Babel 为了解决这个问题,提供了单独的包 babel-runtime 用以提供编译模块的工具函数。将 Babel 的运行时的 “ polyfilling” 行为分开了。也就是说不再污染全局,将运行时的帮助函数抽离出来管理。
使用类转换
插件:
1 | class Circle {} |
如果使用@ babel / plugin-transform-runtime,它将把对该函数的引用替换为 @babel / runtime 版本:
1 | var _classCallCheck = require('@babel/runtime/helpers/classCallCheck') |
.babelrc、babel.config.js、package.json 等配置
.babelrc
使用场景
- 只是需要一个简单的并且只用于单个软件包的配置
在你的项目中创建名为 .babelrc
的文件,并输入以下内容。
1 | { |
babel.config.js
使用场景
- 希望以编程的方式创建配置文件
- 希望编译
node_modules
目录下的模块
在项目的根目录(package.json
文件所在目录)下创建一个名为 babel.config.js
的文件,并输入如下内容。
1 | module.exports = function (api) { |
package.json
如下所示:
1 | { |
插件
@babel/parser:Babel 解析器(以前是 Babylon)是 Babel 中使用的 JavaScript 解析器,以前基于 acorn 库 fork 出来的
@babel/traverse:Babel Traverse(遍历)模块维护了整棵树的状态,并且负责替换、移除和添加节点
@babel/generator:Babel 的代码生成器,它读取 AST 并将其转换为代码
@babel/types:Babel Types 模块是一个用于 AST 节点的 Lodash 式工具库, 它包含了构造、验证以及变换 AST 节点的方法。 该工具库包含考虑周到的工具方法,对编写处理 AST 逻辑非常有用。
1 | // 依赖版本 7.x |
过程
使用插件版本 6.x
举个 🌰
1 | const { parse } = require('babylon') |
解析出的 AST
1 | { |
解析
- 词法分析:把字符串形式的代码转换为令牌(token)
在解析生成节点之前,会使用 state 属性的值生成 token
生成的 token 的样子
- 语法分析:这个阶段会把令牌中的信息转换成 AST 的表述结构
next 里生成 token,finishNode 生成节点
- Parser 实例
实例化的过程中做 state 和其他属性的初始化,之后生成之后调用 parse 方法
1 | function parse(input, options) { |
1 | function Parser(options, input) { |
得到的 Parser 实例
1 | { |
- state 属性
用于在解析过程中,用于保存解析的指针、值与其他位置状态信息等
1 | { |
- readWord1 方法
词法分析过程,根据 unicode 的值比较,遇到\
、.
之类终止循环。
1 | Tokenizer.prototype.readWord1 = function readWord1() { |
- next 方法
更新 state 指针、tokens 状态。最后并且调用 nextToken 开始解析后面的内容
1 | Tokenizer.prototype.next = function next() { |
- nextToken 方法
1 | Tokenizer.prototype.nextToken = function nextToken() { |
- finishToken 方法
词法分析结束,更新 state 属性上的信息
1 | Tokenizer.prototype.finishToken = function finishToken(type, val) { |
- finishNode 方法
生成节点
1 | pp.finishNode = function(node, type) {// 传入的 node 节点上的参数,都是从 state 属性上拿的 |
- finishNodeAt 方法
设置 node(节点)属性,并返回 node
1 | function finishNodeAt(node, type, pos, loc) { // pos loc 也是 state属性 上拿的 |
简单理解
1 | const input = 'a' |
转换
第二个 🌰
1 | const { parse } = require('babylon') |
- visit 方法
会对节点遍历,单个节点和多个节点不同的处理,对具体的节点调用 visit 方法处理
对遍历到的节点处理
1 | function visit() { |
a 节点
call 方法
调用访问者中设置的 enter 方法
生成
第三个 🌰
1 | const { parse } = require('babylon') |
- print 方法
遍历节点
1 | Printer.prototype.print = function print(node, parent) { |
- Identifier 方法
当节点类型是 Identifier 的时候
1 | function Identifier(node) { |
- append 方法
在 _append 方法里,向 _buf 数组里 push 解析出的内容,就是 node.name
1 | Buffer.prototype.append = function append(str) { |
- get 方法
合并并返回结果
1 | Buffer.prototype.get = function get() { |