前端需要掌握的Babel知识
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是啥,因为语法不都给你转换好了,还需要这个东西干啥,后来仔细想了一下,要适应新特性应该从两方面入手:
语法转换:
() => {};
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提供了我们想要的所有新方法新类,但是这里依然存在一些问题:
- 体积太大:比如我只用了String的新特性,但是我把整个包都引进来了,这不是徒增了很多无用的代码。
- 污染全局环境:如果你引用了
@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的配置。所以以下代码的执行顺序为
- @babel/plugin-proposal-decorators
- @babel/plugin-proposal-class-properties
- @babel/plugin-transform-runtime
- @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知识的更多相关文章
- 前端开发:css基础知识之盒模型以及浮动布局。
前端开发:css基础知识之盒模型以及浮动布局 前言 楼主的蛮多朋友最近都在学习html5,他们都会问到同一个问题 浮动是什么东西? 为什么这个浮动没有效果? 这个问题楼主已经回答了n遍.今天则是把 ...
- 前端学习:JS面向对象知识学习(图解)
前端学习:JS面向对象知识学习(图解) 前端学习:JS(面向对象)代码笔记 JS面向对象图解知识全览 创建类和对象 方式1:使用Object()函数 方式2:使用自变量 方式3:使用工厂函数 创建多个 ...
- 前端开发者必备的Nginx知识
摘要: 最常用的Web服务器 -- Nginx 原文:前端开发者必备的Nginx知识 作者:ConardLi Fundebug经授权转载,版权归原作者所有. Nginx在应用程序中的作用 解决跨域 请 ...
- 前端工程师需要掌握的 Babel 知识
在前端圈子里,对于 Babel,大家肯定都比较熟悉了.如果哪天少了它,对于前端工程师来说肯定是个噩梦.Babel 的工作原理是怎样的可能了解的人就不太多了.本文将主要介绍 Babel 的工作原理以及怎 ...
- Web前端工程师成长之路——知识汇总
一.何为Web前端工程师? 前端工程师,也叫Web前端开发工程师.他是随着web发展,细分出来的行业.Web前端开发工程师,主要职责是利用(X)HTML/CSS/JavaScript ...
- 小白到web前端工程师需要学习哪些知识?
随着web3.0时代,那么web前端开发技术人才越来越吃香,而且web前端领域划分越来越细,对技术的需求越来越高,想学习web前端的人也是越来越多.那么,如何学习web前端知识?从哪开始?转型成为we ...
- 前端项目中常用es6知识总结 -- let、const及数据类型延伸
项目开发中一些常用的es6知识,主要是为以后分享小程序开发.node+koa项目开发以及vueSSR(vue服务端渲染)做个前置铺垫. 项目开发常用es6介绍 1.块级作用域 let const 2 ...
- 【前端】之JavaScript基础知识
JS 基础知识 JS中,简单类型的数据存储在栈中,复杂类型的数据存储在堆中,其引用存储在栈中 JS中的深拷贝和浅拷贝: 浅拷贝:将对象中的所有简单类型的属性拷贝出来,引用类型属性直接赋值null 深拷 ...
- 前端必会的js知识总结整理
1.晨曦. 2.js是一门什么样的语言及特点? js是一种基于对象和事件驱动的并具有相对安全性的客户端脚本语言.也是一种广泛用于web客户端开发的脚本语言,常用来给html网页添加动态 ...
随机推荐
- Nginx配置客户端SSL双向认证
对于 NGINX 的 HTTPS 配置,通常情况下我们只需要实现服务端认证就行,因为浏览器内置了一些受信任的证书颁发机构(CA),服务器端只需要拿到这些机构颁发的证书并配置好,浏览器会自己校验证书的可 ...
- 4.1. Scrapy配置安装
Scrapy的安装介绍 Scrapy框架官方网址:http://doc.scrapy.org/en/latest Scrapy中文维护站点:http://scrapy-chs.readthedocs. ...
- c#读取文件路径并保存在textBox2中
private void button1_Click_1(object sender, EventArgs e) { OpenFileDialog openFileDialog1 = new Open ...
- 电脑设置自动关机和取消自动关机代码bat命令
一.设置定时关机bat 1.首先在桌面新建一个记事本txt 2.打开记事本,输入“shutdown -s -t 1260 ”1260是指1260秒,即21分钟内关机.可以修改成别的时间. 3.保存后, ...
- LoadRunner(3)
一.性能测试的策略 重要的:基准测试.并发测试.在线综合场景测试 递增测试.极限测试... 1.基准测试:Benchmark Testing 含义:就是单用户测试,单用户.单测试点.执行n次: 作为后 ...
- 双端队列 C. Vasya and String
High school student Vasya got a string of length n as a birthday present. This string consists of le ...
- python 判断数据类型及释疑
Python 判断数据类型有type和isinstance 基本区别在于: type():不会认为子类是父类 isinstance():会认为子类是父类类型 class Color(object): ...
- 一个 TCP 连接可以发多少个 HTTP 请求?
曾经有这么一道经典面试题:从 URL 在浏览器被被输入到页面展现的过程中发生了什么?相信大多数准备过的同学都能回答出来,但是如果继续问:收到的 HTML 如果包含几十个图片标签,这些图片是以什么方式. ...
- java中list和map的底层实现原理
Collection(单列集合) List(有序,可重复) ArrayList 底层数据结构是数组,查询快,增删慢 线程不安全,效率高 Vector 底层数据结构是数组,查询快,增删慢 线程安全,效率 ...
- Mapreduce案例之Pi值估算
题目: 这个程序的原理是这样的.假如有一个边长为1的正方形.以正方形的一个端点为圆心,以1为半径,画一个圆弧,于是在正方形内就有了一个直角扇形.在正方形里随机生成若干的点,则有些点是在扇形内,有些点是 ...