在前端开发领域,浏览器兼容性问题从来不曾消失。除了 CSS,我们还要面对 JavaScript 的兼容性问题。

不同的浏览器讲着不同的 JavaScript 语言,不同的浏览器版本同样讲着不同的 JavaScript 语言。

你用了 JavaScript 的 A 特性,能够在 B 浏览器上正常运行,却在 C 浏览器的 D 版本上报错。

这正是 Babel.js 要解决的问题。更进一步,它能够让所有浏览器上都还不能正常运行的特性正常运行在所有浏览器上。

也因此,Babel 项目非常庞大,而且在不断地更新、调整,这意味着,一篇教程不可能囊括所有 - 当然,我也没那种打算。

本文基于 babel 7.0.0-beta.39。

babel-cli

babel-cli 是 babel 提供的命令行工具,用于命令行下编译我们的源代码 - 至于脚本语言用“编译”一词是否合适,我们且留给他人去论证。

这里假定我们已经初始化一个项目。

执行如下命令可以在项目下安装 babel-cli

  1. npm install --save-dev @babel/core @babel/cli

嗯?怎么不是 npm install --save-dev babel-cli@ 符号又是什么意思?这正是 babel 7 的一大调整,从原来的 babel-xx 的包命名格式迁移到域内包(scoped package)。如果你有兴趣,可以阅读 babel 博客上重命名包名称的说明

假定我们的项目下有一个 babel.test.js 文件,内容是:

  1. let fun = () => console.log('hello babel.js')

我们试试运行 npx babel babel.test.js

  1. $ npx babel babel.test.js
  2. let fun = () => console.log('hello babel.js');

还是原来的代码,没有任何变化。说好的编译呢?

这个调整则是在 babel 6 里发生的。Babel 6 做了大量模块化的工作,将原来集成一体的各种编译功能分离出去,独立成插件。这意味着,默认情况下,当下版本的 babel 不会编译代码。

babel 插件

换句话说,我们要将上面的箭头函数编译成 ES5 函数,需要额外安装 babel 插件。

首先,安装 @babel/plugin-transform-arrow-functions

  1. npm install --save-dev @babel/plugin-transform-arrow-functions

然后,在命令行编译时指定使用该插件:

  1. $ npx babel babel.test.js --plugins @babel/plugin-transform-arrow-functions
  2. let fun = function () {
  3. return console.log('hello babel.js');
  4. };

编译成功。

配置文件 .babelrc

随着各种新插件的加入,我们的命令行参数只会越来越长。

这时,我们可以新建一个 .babelrc 文件,把各种命令行参数统一到其中。

比如,要配置前面提到过的箭头函数插件:

  1. {
  2. "plugins": ["@babel/plugin-transform-arrow-functions"]
  3. }

之后,在命令行只要运行 npx babel babel.test.js 即可,babel 会自动读取 .babelrc 里的配置并应用到编译中:

  1. $ npx babel babel.test.js
  2. let fun = function () {
  3. return console.log('hello babel.js');
  4. };

babel 套餐

我们有一个项目,页面要求支持 IE 10,但 IE 10 不支持箭头函数、classconst,可是你喜欢用这些新增的 JavaScript 语法,你在项目里写了这么一段代码:

  1. const alertMe = (msg) => {
  2. window.alert(msg)
  3. }
  4. class Robot {
  5. constructor (msg) {
  6. this.message = msg
  7. }
  8. say () {
  9. alertMe(this.message)
  10. }
  11. }
  12. const marvin = new Robot('hello babel')

显然,在 IE 10 下这段代码报错了。

好消息是,babel 有各类插件满足你的上述需求。

我们来安装相应的插件:

  1. $ npm install --save-dev @babel/plugin-transform-arrow-functions @babel/plugin-transform-block-scoping @babel/plugin-transform-classes

接着,将它们加入 .babelrc 配置文件中:

  1. {
  2. "plugins": [
  3. "@babel/plugin-transform-arrow-functions",
  4. "@babel/plugin-transform-block-scoping",
  5. "@babel/plugin-transform-classes"
  6. ]
  7. }

然后运行 npx babel babel.test.js,就有编译结果了:

  1. var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
  2. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  3. var alertMe = function (msg) {
  4. window.alert(msg);
  5. };
  6. var Robot = function () {
  7. function Robot(msg) {
  8. _classCallCheck(this, Robot);
  9. this.message = msg;
  10. }
  11. _createClass(Robot, [{
  12. key: 'say',
  13. value: function say() {
  14. alertMe(this.message);
  15. }
  16. }]);
  17. return Robot;
  18. }();
  19. var marvin = new Robot('hello babel');

只是,这样安装插件、配置 .babelrc 的过程非常乏味,而且容易出错。通常,我们不会关心到具体的某个 ES2015 特性支持情况这个层面,我们更关心浏览器版本这个层面。

你说,我不想关心 babel 插件的配置,我只希望,给 babel 一个我想支持 IE 10 的提示,babel 就帮我编译出能在 IE 10 上正常运行的 JavaScript 代码。

欢迎 @babel/preset-env

等等,Preset 是什么?前面我们已经认识了插件,那么不妨把 Preset 理解为套餐,每个套餐里打包了不同的插件,这样安装套餐就等于一次性安装各类 babel 插件。

我们来看看怎样使用 @babel/preset-env

首先在项目下安装:

  1. $ npm install --save-dev @babel/preset-env

然后修改 .babelrc

  1. {
  2. "presets": ["@babel/preset-env"]
  3. }

运行 npx babel babel.test.js,输出结果如下:

  1. 'use strict';
  2. var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
  3. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  4. var alertMe = function alertMe(msg) {
  5. window.alert(msg);
  6. };
  7. var Robot = function () {
  8. function Robot(msg) {
  9. _classCallCheck(this, Robot);
  10. this.message = msg;
  11. }
  12. _createClass(Robot, [{
  13. key: 'say',
  14. value: function say() {
  15. alertMe(this.message);
  16. }
  17. }]);
  18. return Robot;
  19. }();
  20. var marvin = new Robot('hello babel');

Wow,与前面辛苦配置各种插件后的输出结果几乎一模一样。

可是,我们还没告诉 babel 我们要支持 IE 10 的,为什么它却好像预知一切?

我们来看 babel-preset-env 的一段文档:

Without any configuration options, babel-preset-env behaves exactly the same as babel-preset-latest (or babel-preset-es2015, babel-preset-es2016, and babel-preset-es2017 together).

默认情况下,babel-preset-env 等效于三个套餐,而不巧我们前面安装的几个插件已经囊括在 babel-preset-es2015 中。

那么,如果我只想支持最新版本的 Chrome 呢?

这时我们可以调整 .babelrc 的配置:

  1. {
  2. "presets": [
  3. ["@babel/preset-env", {
  4. "targets": {
  5. "browsers": ["last 1 Chrome versions"]
  6. }
  7. }]
  8. ]
  9. }

再次编译,结果如下:

  1. $ npx babel babel.test.js
  2. 'use strict';
  3. const alertMe = msg => {
  4. window.alert(msg);
  5. };
  6. class Robot {
  7. constructor(msg) {
  8. this.message = msg;
  9. }
  10. say() {
  11. alertMe(this.message);
  12. }
  13. }
  14. const marvin = new Robot('hello babel');

最新版本的 Chrome 已经支持箭头函数、classconst,所以 babel 在编译过程中,不会编译它们。这也是为什么我们把 @babel/preset-env 称为 JavaScript 的 Autoprefixer。

babel-polyfill

Babel includes a polyfill that includes a custom regenerator runtime and core-js.

基本上,babel-polyfill 就是 regenerator runtime 加 core-js

可是,为什么需要 polyfill 这所谓的垫片?前面聊到 @babel/preset-env 时,不是说只要定义好我想支持的目标浏览器,babel 就能编译出能运行在目标浏览器上的代码吗?

我们暂时去掉 babel-,从 polyfill 说起。

拿 findIndex 来说,IE 11 仍不支持该方法,假如你的代码里写了 findIndex,IE 11 浏览器会报如下错误:

  1. Object doesn't support property or method 'findIndex'

怎么办,这时我们就可以写个 polyfill

  1. // https://tc39.github.io/ecma262/#sec-array.prototype.findIndex
  2. if (!Array.prototype.findIndex) {
  3. Object.defineProperty(Array.prototype, 'findIndex', {
  4. value: function(predicate) {
  5. // 1. Let O be ? ToObject(this value).
  6. if (this == null) {
  7. throw new TypeError('"this" is null or not defined');
  8. }
  9. var o = Object(this);
  10. // 2. Let len be ? ToLength(? Get(O, "length")).
  11. var len = o.length >>> 0;
  12. // 3. If IsCallable(predicate) is false, throw a TypeError exception.
  13. if (typeof predicate !== 'function') {
  14. throw new TypeError('predicate must be a function');
  15. }
  16. // 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
  17. var thisArg = arguments[1];
  18. // 5. Let k be 0.
  19. var k = 0;
  20. // 6. Repeat, while k < len
  21. while (k < len) {
  22. // a. Let Pk be ! ToString(k).
  23. // b. Let kValue be ? Get(O, Pk).
  24. // c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)).
  25. // d. If testResult is true, return k.
  26. var kValue = o[k];
  27. if (predicate.call(thisArg, kValue, k, o)) {
  28. return k;
  29. }
  30. // e. Increase k by 1.
  31. k++;
  32. }
  33. // 7. Return -1.
  34. return -1;
  35. }
  36. });
  37. }

如果目标环境中已经存在 findIndex,我们什么都不做,如果没有,我们就在 Array 的原型中定义一个。这便是 polyfill 的意义。babel-polyfill 同理。

虽说浏览器的特性支持状况千差万别,但其实可以提炼出两类:

  1. 大家都有,只是 A 语法与 B 语法的区别;
  2. 不是大家都有:有的有,有的没有。

babel 编译过程处理第一种情况 - 统一语法的形态,通常是高版本语法编译成低版本的,比如 ES6 语法编译成 ES5 或 ES3。而 babel-polyfill 处理第二种情况 - 让目标浏览器支持所有特性,不管它是全局的,还是原型的,或是其它。这样,通过 babel-polyfill,不同浏览器在特性支持上就站到同一起跑线。

下面我们看看 babel-polyfill 的用法。

安装 babel-polyfill

  1. $ npm install --save @babel/polyfill

使用 babel-polyfill

我们需要在程序入口文件的顶部引用 @babel-polyfill

  1. require('@babel/polyfill')
  2. [].findIndex('babel')

或者使用 ES6 的写法:

  1. import '@babel/polyfill'
  2. [].findIndex('babel')

需要注意的是,babel-polyfill 不能多次引用。如果我们的代码中有多个 require('@babel/polyfill'),则执行时会报告错误:

  1. only one instance of @babel/polyfill is allowed

这是因为引入的 babel-polyfill 会在全局写入一个 _babelPolyfill 变量。第二次引入时,会检测该变量是否已存在,如果已存在,则抛出错误。

注意事项

如前面所说的,babel-polyfill 其实包含 regenerator runtimecore-js,如果你的代码只需要其中一部分 polyfill,那么你可以考虑直接引入 core-js 下的特定 polyfill,不必使用 babel-polyfill 这样的庞然大物。

babel-runtime

babel-runtime 是 babel 生态里最让人困惑的一个包。

我们先来看看它的 package.json 里的 description 怎么写:

babel selfContained runtime

有点不知所谓。

不过从 package.json 里没有 main 字段我们可以看出,它的用法肯定不是 require('babel-runtime') 这样。

我们再看包依赖:

  1. "dependencies": {
  2. "core-js": "^2.5.3",
  3. "regenerator-runtime": "^0.11.1"
  4. }

跟 babel-polyfill 的包依赖 一模一样。

这正是让人疑惑不解的地方,为什么要有两个不同名称却相同依赖的包?它们的目的是否一样,是否能够共用?

我们拿 Object.assign 为例,剖析下 babel-polyfill 与 babel-runtime 的异同。

我们知道,IE 11 不支持 Object.assign,此时,我们有俩种候选方案:

  1. 引入 babel-polyfill,这样 Object.assign 就会被创造出来
  2. 配置 @babel/plugin-transform-object-assign

第二种方案中,babel 会将所有的 Object.assign 替换成 _extends 这样一个辅助函数。如下所示:

  1. Object.assign({}, {})

编译成:

  1. function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
  2. _extends({}, {});

问题是,如果你的项目里有 100 个文件,其中有 50 个文件里写了 Object.assign,那么,坏消息来了,_extends 辅助函数会出现 50 次。

怎么办?我们自然而然会想到把 _extends 分离出去,然后在每个文件中引入 - 这正是 babel-runtime 的作用:

  1. var _extends = require("@babel/runtime/helpers/extends");
  2. _extends({}, {});

非常漂亮。可没人想要手动转换这些代码。

babel 提供了 @babel/plugin-transform-runtime 来做这些转换。

@babel/plugin-transform-runtime

我们首先安装插件:

  1. $ npm install --save-dev @babel/plugin-transform-runtime

然后再安装 babel-runtime

  1. $ npm install @babel/runtime

最后在 .babelrc 中配置:

  1. {
  2. "plugins": [
  3. "@babel/plugin-transform-object-assign",
  4. "@babel/plugin-transform-runtime"
  5. ]
  6. }

这样,我们不需要 babel-polyfill 也一样可以在程序中使用 Object.assign,编译后的代码最终能够正常运行在 IE 11 下。

提问:在经过 @babel/plugin-transform-runtime 的处理后,IE 11 下现在有 Object.assign吗?

答案是,仍然没有。

这正是 babel-polyfill 与 babel-runtime 的一大区别,前者改造目标浏览器,让你的浏览器拥有本来不支持的特性;后者改造你的代码,让你的代码能在所有目标浏览器上运行,但不改造浏览器。

如果你还是困惑,我推荐一个非常简单的区分方法 - 打开浏览器开发者工具,在 console 里执行代码:

  1. 引入 babel-polyfill 后的 IE 11,你可以在 console 下执行 Object.assign({}, {})
  2. 而引入 babel-runtime 后的 IE 11,仍然提示你:Object doesn't support property or method 'assign'

再回到我们前面提出的一个问题:babel-polyfill 是否可以跟 babel-runtime 共用?

这个问题,且留给读者们自己探索。

babel-register

经过 babel 的编译后,我们的源代码与运行在生产下的代码是不一样的。

babel-register 则提供了动态编译。换句话说,我们的源代码能够真正运行在生产环境下,不需要 babel 编译这一环节。

我们先在项目下安装 babel-register

  1. $ npm install --save-dev @babel/register

然后在入口文件中 require

  1. require('@babel/register')
  2. require('./app')

在入口文件头部引入 @babel/register 后,我们的 app 文件中即可使用任意 es2015 的特性。

当然,坏处是动态编译,导致程序在速度、性能上有所损耗。

babel-node

我们上面说,babel-register 提供动态编译,能够让我们的源代码真正运行在生产环境下 - 但其实不然,我们仍需要做部分调整,比如新增一个入口文件,并在该文件中 require('@babel/register')。而 babel-node 能真正做到一行源代码都不需要调整:

  1. $ babel-node app.js

只是,请不要在生产环境中使用 babel-node,因为它是动态编译源代码,应用启动速度非常慢。

[转] babel 教程的更多相关文章

  1. Babel与Polyfill的关系和区别

    很多同学搞不清楚babel与polyfill的关系以及区别,这儿给大家细致解惑. Babel:Babel 是一个广泛使用的 ES6 转码器,可以将 ES6 代码转为 ES5 代码.注意:Babel 默 ...

  2. 前端需要掌握的Babel知识

    Babel 是怎么工作的 Babel 是一个 JavaScript 编译器. 做与不做 注意很重要的一点就是,Babel 只是转译新标准引入的语法,比如: 箭头函数 let / const 解构 哪些 ...

  3. let import export React入门实例教程 connect provider combineReducers 箭头函数 30分钟掌握ES6/ES2015核心内容 Rest babel

    let与var的区别 http://www.cnblogs.com/snandy/archive/2015/05/10/4485832.html es6 导入导出 http://www.csdn.ne ...

  4. window搭建webpack,react,babel傻瓜教程

    首先现在的webpack教程已经很多了,写这篇的原因是因为自己在从小白开始的搭建过程中,并没有找到比较好的教程,花费了很多的时间,so 有了这篇博客,方便小白同学学习. node环境在这里不在赘述,p ...

  5. Webpack使用教程五(Babel)

    Babel是一个JavaScript编译和工具平台,使用Babel我们可以:使用新版本的JavaScript(ES6/ES2015,ES7/ES2016),尽管有些浏览器不能全部支持新特性:使用Jav ...

  6. Babel 安装教程

    Babel是一个广泛使用的转码器,可以将ES6代码转为ES5代码,从而在现有环境执行. 这意味着,你可以现在就用 ES6 编写程序,而不用担心现有环境是否支持.下面是一个例子. // 转码前 inpu ...

  7. 《转》Babel 入门教程

    ECMAScript 6是JavaScript语言的下一代标准,已经在2015年6月正式发布了.Mozilla公司将在这个标准的基础上,推出JavaScript 2.0.ES6的目标,是使得JavaS ...

  8. Babel 入门教程

    Babel是一个广泛使用的转码器,可以将ES6代码转为ES5代码,从而在现有环境执行. 这意味着,你可以现在就用 ES6 编写程序,而不用担心现有环境是否支持.下面是一个例子. // 转码前 inpu ...

  9. babel初学教程

    babel安装 安装前你需要安装node.js和npm以及gulp三个包. 然后执行以下命令 $ npm init 然后在安装 babel和babel-core两个包 $ npm install -- ...

随机推荐

  1. MII、RMII、GMII接口的详细介绍【转】

    转自:https://www.cnblogs.com/geekite/p/5204512.html 概述: MII (Media Independent Interface(介质无关接口)或称为媒体独 ...

  2. 《超越C++标准库:Boost库导引》:序

    序(Foreword) C++社区正在发生着一些美妙的事情.尽管C++仍然是世界上使用最广泛的编程语言,它依旧在变得更加强大而且易用.不信么?容我慢慢道来. 当前版本的标准C++是在1998年最终确定 ...

  3. 洛谷 [USACO17OPEN]Bovine Genomics G奶牛基因组(金) ———— 1道骗人的二分+trie树(其实是差分算法)

    题目 :Bovine Genomics G奶牛基因组 传送门: 洛谷P3667 题目描述 Farmer John owns NN cows with spots and NN cows without ...

  4. tomcat 嵌入式

    背景 开源世界真是有意思,竟然还有这种玩法.以前一直想bs程序如何像cs程序作为安装包形式,这个就是个解决方案. 知识点 将tomcat嵌入到主程序中进行运行,而不是像以前将一个web项目copy到t ...

  5. Debian下undefined reference to ‘pthread_create’问题解决

    今天在写线程测试程序(pthread_create)时出现如下问题, 明明在头文件中包含了<pthread.h>,但是仍然提示找不到函数 pthread_create 和 pthread_ ...

  6. ES--04

    第三十一讲! 分布式文档系统 写一致性原理以及相关参数 课程大纲 (1)consistency,one(primary shard),all(all shard),quorum(default) 我们 ...

  7. java8 常用函数式接口

    public static void main(String[] args) { // TODO Auto-generated method stub //函数式接口 Function<Inte ...

  8. Jmeter之响应结果乱码解决

    场景: 在测试过程中,我们可能需要查看结果树,但是发现里面的响应数据在“Document”以外的其他表现形式下都有乱码,如下图就是设置了以Text的形式展示,响应数据包含乱码: 分析:原因是Jmete ...

  9. Codeforces 280D k-Maximum Subsequence Sum [模拟费用流,线段树]

    洛谷 Codeforces bzoj1,bzoj2 这可真是一道n倍经验题呢-- 思路 我首先想到了DP,然后矩阵,然后线段树,然后T飞-- 搜了题解之后发现是模拟费用流. 直接维护选k个子段时的最优 ...

  10. Java红黑树详谈

    定义 红黑树的主要是想对2-3查找树进行编码,尤其是对2-3查找树中的3-nodes节点添加额外的信息.红黑树中将节点之间的链接分为两种不同类型,红色链接,他用来链接两个2-nodes节点来表示一个3 ...