Babel 是怎么工作的

Babel 是一个 JavaScript 编译器。

做与不做

注意很重要的一点就是,Babel 只是转译新标准引入的语法,比如:

  • 箭头函数
  • let / const
  • 解构

哪些在 Babel 范围外?对于新标准引入的全局变量、部分原生对象新增的原型链上的方法,Babel 表示超纲了。

  • 全局变量
  • Promise
  • Symbol
  • WeakMap
  • Set
  • includes
  • generator 函数

对于上面的这些 API,Babel 是不会转译的,需要引入 polyfill 来解决。

Babel 编译的三个阶段

Babel 的编译过程和大多数其他语言的编译器相似,可以分为三个阶段:

  • 解析(Parsing):将代码字符串解析成抽象语法树。
  • 转换(Transformation):对抽象语法树进行转换操作。
  • 生成(Code Generation): 根据变换后的抽象语法树再生成代码字符串。

为了理解 Babel,我们从最简单一句 console 命令下手

解析(Parsing)

Babel 拿到源代码会把代码抽象出来,变成 AST (抽象语法树),学过编译原理的同学应该都听过这个词,全称是 Abstract Syntax Tree

抽象语法树是源代码的抽象语法结构的树状表示,树上的每个节点都表示源代码中的一种结构,只所以说是抽象的,是因为抽象语法树并不会表示出真实语法出现的每一个细节,

比如说,嵌套括号被隐含在树的结构中,并没有以节点的形式呈现,它们主要用于源代码的简单转换。

console.log('zcy'); 的 AST 长这样:

{
"type": "Program",
"body": [
{
"type": "ExpressionStatement",
"expression": {
"type": "CallExpression",
"callee": {
"type": "MemberExpression",
"computed": false,
"object": {
"type": "Identifier",
"name": "console"
},
"property": {
"type": "Identifier",
"name": "log"
}
},
"arguments": [
{
"type": "Literal",
"value": "zcy",
"raw": "'zcy'"
}
]
}
}
],
"sourceType": "script"
}
上面的 AST 描述了源代码的每个部分以及它们之间的关系,可以自己在这里试一下 astexplorer
AST 是怎么来的?
整个解析过程分为两个步骤:
  • 分词:将整个代码字符串分割成语法单元数组  在线分词工具
语法单元通俗点说就是代码中的最小单元,不能再被分割,就像原子是化学变化中的最小粒子一样。Javascript 代码中的语法单元主要包括以下这么几种:
  • 关键字:const、 let、  var 等
  • 标识符:可能是一个变量,也可能是 if、else 这些关键字,又或者是 true、false 这些常量
  • 运算符
  • 数字
  • 空格
  • 注释:对于计算机来说,知道是这段代码是注释就行了,不关心其具体内容

其实分词说白了就是简单粗暴地对字符串一个个遍历。为了模拟分词的过程,写了一个简单的 Demo,仅仅适用于和上面一样的简单代码。Babel 的实现比这要复杂得多,但是思路大体上是相同的。

  • 语法分析:建立分析语法单元之间的关系
语义分析是将得到的词汇进行一个立体的组合,确定词语之间的关系。考虑到编程语言的各种从属关系的复杂性,语义分析的过程又是在遍历得到的语法单元组,相对而言就会变得更复杂。
简单来说语法分析是对语句和表达式识别,这是个递归过程,在解析中,Babel  会在解析每个语句和表达式的过程中设置一个暂存器,用来暂存当前读取到的语法单元,如果解析失败,就会返回之前的暂存点,再按照另一种方式进行解析,
如果解析成功,则将暂存点销毁,不断重复以上操作,直到最后生成对应的语法树。

转换(Transformation)

Plugins
插件应用于 babel 的转译过程,尤其是第二个阶段 Transformation,如果这个阶段不使用任何插件,那么 babel 会原样输出代码。
Presets
plugins是一个小型的js代码程序,告诉Babel如何转换你的源码,比如 @babel/plugin-transform-arrow-functions 的作用就是将es2015的箭头函数转换成普通函数:
有那么多新的语法, 我们总不能一个一个的引入吧,于是就产生了预设: Presets,

顾名思义——预设,它包含了一组我们需要的plugins Babel 官方帮我们做了一些预设的插件集,称之为 Preset
这样我们只需要使用对应的 Preset 就可以了, 而 babel-preset-env 相当于 ES2015 ,ES2016 ,ES2017 及最新版本。
Polyfill

  中文翻译是垫片,之前没有详细了解babel之前,我也很迷茫这个polyfill是啥,因为语法不都给你转换好了,还需要这个东西干啥,后来仔细想了一下,要适应新特性应该从两方面入手:

  1. 语法转换:
() => {};

for (let i of items) {};

比如箭头函数、for...of,在不支持这些语法的环境下,直接会报语法错误,因为编译器根本不知道 =>这些是什么鬼符号,要做到让编译器识别,那就要将这样的语法转换成浏览器能识别的代码,那么就需要语法转换。

2.功能补充

  比如 'foo'.includes('f'), es2015里不仅只有新的语法,还有实例的扩展,比如String,其实这里只是调用了String实例的一个方法,我们无论怎么语法转换也没有什么用吧,如果我们在不支持String.prototype.includes的编译器里跑这些代码,会得到         'foo'.includes is not a function. 这样的一个报错,而不是语法报错。

Polyfill提供的就是一个这样功能的补充,实现了Array、Object等上的新方法,实现了Promise、Symbol这样的新Class等。

虽然@babel/polyfill提供了我们想要的所有新方法新类,但是这里依然存在一些问题:

  1. 体积太大:比如我只用了String的新特性,但是我把整个包都引进来了,这不是徒增了很多无用的代码。
  2. 污染全局环境:如果你引用了 @babel/polyfill,那么像Promise这样的新类就是挂载在全局上的,这样就会污染了全局命名空间。可能在一个团建建立的项目问题不太大,但是如果你是一个工具的开发者,你把全局环境污染了,别人用你的工具,就有可能把别人给坑了。

一个解决方案就是引入transform runtime 来替代 @babel/polyfill,像下面这样的配置:

{
"plugins": [
["transform-runtime", {
"helpers": false, //自动引入helpers
"polyfill": false, //自动引入polyfill(core-js提供的polyfill)
"regenerator": true, //自动引入regenerator
}]
]
}

另一个解决方案就是  @babel/preset-env 这个preset,它有一个useBuiltIns选项,如果设置成"usage",那么将会自动检测语法帮你require你代码中使用到的功能。

const presets = [
[
"@babel/env",
{
useBuiltIns: "usage",
},
],
];

比如我在代码中:

Promise.resolve().finally();

如果在edge17不支持这个特性的环境里运行,将会帮你编译成:

require("core-js/modules/es.promise.finally");

Promise.resolve().finally();
比较 transform-runtime 与 babel-polyfill 引入垫片的差异:

  1.使用runtime是按需引入,需要用到哪些polyfill,runtime就自动帮你引入哪些,不需要再手动一个个的去配置plugins,只是引入的polyfill不是全局性的,有些局限性。而且runtime引入的polyfill不会改写一些实例方法,比如Object和Array原型链上的方法,像前面提到的Array.protype.includes。

  2.babel-polyfill就能解决runtime的那些问题,它的垫片是全局的,而且全能,基本上ES6中要用到的polyfill在babel-polyfill中都有,它提供了一个完整的ES6+的环境。babel官方建议只要不在意babel-polyfill的体积,最好进行全局引入,因为这是最稳妥的方式。

  3.一般的建议是开发一些框架或者库的时候使用不会污染全局作用域的babel-runtime,而开发web应用的时候可以全局引入babel-polyfill避免一些不必要的错误,而且大型web应用中全局引入babel-polyfill可能还会减少你打包后的文件体积(相比起各个模块引入重复的polyfill来说)。

Plugin/Preset 路径
如果 Plugin 是通过 npm 安装,可以传入 Plugin 名字给 Babel,Babel 将检查它是否安装在 node_modules 中
"plugins": ["babel-plugin-myPlugin"]
也可以指定你的 Plugin/Preset 的相对或绝对路径。
"plugins": ["./node_modules/asdf/plugin"]
Plugin/Preset 排序
如果两次转译都访问相同的节点,则转译将按照 Plugin 或 Preset 的规则进行排序然后执行。
  • Plugin 会运行在 Preset 之前。
  • Plugin 会从第一个开始顺序执行。
  • Preset 的顺序则刚好相反(从最后一个逆序执行)。
例如:
{
"plugins": [
"transform-decorators-legacy",
"transform-class-properties"
]
}
将先执行 transform-decorators-legacy 再执行 transform-class-properties但 preset 是反向的
{
"presets": [
"es2015",
"react",
"stage-2"
]
}
会按以下顺序运行:  stage-2, react, 最后 es2015
那么问题来了,如果 presets 和 plugins 同时存在,那执行顺序又是怎样的呢?答案是先执行 plugins 的配置,再执行 presets 的配置。所以以下代码的执行顺序为
  1. @babel/plugin-proposal-decorators
  2. @babel/plugin-proposal-class-properties
  3. @babel/plugin-transform-runtime
  4. @babel/preset-env
// .babelrc 文件
{
"presets": [
[
"@babel/preset-env"
]
],
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose": true }],
"@babel/plugin-transform-runtime",
]
}
生成(Code Generation)
用 babel-generator 通过 AST 树生成 ES5 代码

演示代码地址:点我

参考 :

前端工程师必须掌握的Babel知识  , Babel 7.1介绍  , Babel教程 , Babel该如何配置

前端需要掌握的Babel知识的更多相关文章

  1. 前端开发:css基础知识之盒模型以及浮动布局。

    前端开发:css基础知识之盒模型以及浮动布局 前言 楼主的蛮多朋友最近都在学习html5,他们都会问到同一个问题 浮动是什么东西?  为什么这个浮动没有效果?  这个问题楼主已经回答了n遍.今天则是把 ...

  2. 前端学习:JS面向对象知识学习(图解)

    前端学习:JS面向对象知识学习(图解) 前端学习:JS(面向对象)代码笔记 JS面向对象图解知识全览 创建类和对象 方式1:使用Object()函数 方式2:使用自变量 方式3:使用工厂函数 创建多个 ...

  3. 前端开发者必备的Nginx知识

    摘要: 最常用的Web服务器 -- Nginx 原文:前端开发者必备的Nginx知识 作者:ConardLi Fundebug经授权转载,版权归原作者所有. Nginx在应用程序中的作用 解决跨域 请 ...

  4. 前端工程师需要掌握的 Babel 知识

    在前端圈子里,对于 Babel,大家肯定都比较熟悉了.如果哪天少了它,对于前端工程师来说肯定是个噩梦.Babel 的工作原理是怎样的可能了解的人就不太多了.本文将主要介绍 Babel 的工作原理以及怎 ...

  5. Web前端工程师成长之路——知识汇总

    一.何为Web前端工程师?          前端工程师,也叫Web前端开发工程师.他是随着web发展,细分出来的行业.Web前端开发工程师,主要职责是利用(X)HTML/CSS/JavaScript ...

  6. 小白到web前端工程师需要学习哪些知识?

    随着web3.0时代,那么web前端开发技术人才越来越吃香,而且web前端领域划分越来越细,对技术的需求越来越高,想学习web前端的人也是越来越多.那么,如何学习web前端知识?从哪开始?转型成为we ...

  7. 前端项目中常用es6知识总结 -- let、const及数据类型延伸

    项目开发中一些常用的es6知识,主要是为以后分享小程序开发.node+koa项目开发以及vueSSR(vue服务端渲染)做个前置铺垫. 项目开发常用es6介绍 1.块级作用域 let const  2 ...

  8. 【前端】之JavaScript基础知识

    JS 基础知识 JS中,简单类型的数据存储在栈中,复杂类型的数据存储在堆中,其引用存储在栈中 JS中的深拷贝和浅拷贝: 浅拷贝:将对象中的所有简单类型的属性拷贝出来,引用类型属性直接赋值null 深拷 ...

  9. 前端必会的js知识总结整理

    1.晨曦. 2.js是一门什么样的语言及特点?         js是一种基于对象和事件驱动的并具有相对安全性的客户端脚本语言.也是一种广泛用于web客户端开发的脚本语言,常用来给html网页添加动态 ...

随机推荐

  1. webstorm 注册服务器

    之前都是使用2017.2.27的方法,版本是2017.1.1,还没提示过期,但是根据评论说这个链接已经失效了,评论也给出了个新地址:http://idea.iteblog.com/key.php

  2. Mysql(四):数据操作

    一 介绍 MySQL数据操作: DML ======================================================== 在MySQL管理软件中,可以通过SQL语句中的 ...

  3. redis—django-redis

    自定义连接池 这种方式跟普通py文件操作redis一样,代码如下: views.py import redis from django.shortcuts import render,HttpResp ...

  4. 01.CNN调参

    转载:调参是个头疼的事情,Yann LeCun.Yoshua Bengio和Geoffrey Hinton这些大牛为什么能够跳出各种牛逼的网络? 下面一些推荐的书和文章:调参资料总结Neural Ne ...

  5. 2017 Chinese Multi-University Training, BeihangU Contest

    2017 Chinese Multi-University Training, BeihangU Contest Add More Zero 思路:log10(2^m) = m*log10(2) 代码 ...

  6. Java&Selenium&JS&AWT之那些难以点击到的按钮

    一.摘要 本篇博文的重点并不是简单的click()方法,而是要讲的是那些click()方法失效的时候的处理方式,其实做自动化久了我们都能发现研发的代码并不是都那么美丽,selenium支持的8种定位方 ...

  7. Ubuntu 18.04实现实时显示网速

    1.添加源 sudo add-apt-repository ppa:fossfreedom/indicator-sysmonitor 2.更新源 sudo apt-get update 3.安装sys ...

  8. UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd0 in position 140: invalid continuation byte

    web阅片系统,遇到dicom文件在文件夹不能正常读取的问题.解决方法如下: def rep7(request): file_path = os.path.dirname(__file__) + re ...

  9. java.util.Stack

    import java.util.Stack; public class Test { public static void main(String[] args) { Stack stack = n ...

  10. Transformer的PyTorch实现--转载

    转载自 https://blog.csdn.net/stupid_3/article/details/83184691