本文共 3732 字,大约阅读时间需要 12 分钟。
这篇文章是 的部分内容翻译,结合自己的理解,旨在梳理清楚关于 Babel
是什么、如何运行、什么是插件和预设,以及如何在实际开发中使用等一系列的相关问题。
阮一峰的《ECMAScript 6 入门》一书中对 JavaScript 版本演进过程进行了详细介绍,以下是对其中部分内容的摘抄,原文在此 。
一个常见的问题是,ECMAScript 和 JavaScript 到底是什么关系?
要讲清楚这个问题,需要回顾历史。1996 年 11 月,JavaScript 的创造者 Netscape 公司,决定将 JavaScript 提交给标准化组织 ECMA,希望这种语言能够成为国际标准。次年,ECMA 发布 262 号标准文件(ECMA-262)的第一版,规定了浏览器脚本语言的标准,并将这种语言称为 ECMAScript,这个版本就是 1.0 版。
该标准从一开始就是针对 JavaScript 语言制定的,但是之所以不叫 JavaScript,有两个原因。一是商标,Java 是 Sun 公司的商标,根据授权协议,只有 Netscape 公司可以合法地使用 JavaScript 这个名字,且 JavaScript 本身也已经被 Netscape 公司注册为商标。二是想体现这门语言的制定者是 ECMA,不是 Netscape,这样有利于保证这门语言的开放性和中立性。
因此,ECMAScript 和 JavaScript 的关系是,前者是后者的规格,后者是前者的一种实现(另外的 ECMAScript 方言还有 Jscript 和 ActionScript)。日常场合,这两个词是可以互换的。
ECMAScript 2015(简称 ES2015)这个词,也是经常可以看到的。它与 ES6 是什么关系呢?
2011 年,ECMAScript 5.1 版发布后,就开始制定 6.0 版了。因此,ES6 这个词的原意,就是指 JavaScript 语言的下一个版本。
但是,因为这个版本引入的语法功能太多,而且制定过程当中,还有很多组织和个人不断提交新功能。事情很快就变得清楚了,不可能在一个版本里面包括所有将要引入的功能。常规的做法是先发布 6.0 版,过一段时间再发 6.1 版,然后是 6.2 版、6.3 版等等。
但是,标准的制定者不想这样做。他们想让标准的升级成为常规流程:任何人在任何时候,都可以向标准委员会提交新语法的提案,然后标准委员会每个月开一次会,评估这些提案是否可以接受,需要哪些改进。如果经过多次会议以后,一个提案足够成熟了,就可以正式进入标准了。这就是说,标准的版本升级成为了一个不断滚动的流程,每个月都会有变动。
标准委员会最终决定,标准在每年的 6 月份正式发布一次,作为当年的正式版本。接下来的时间,就在这个版本的基础上做改动,直到下一年的 6 月份,草案就自然变成了新一年的版本。这样一来,就不需要以前的版本号了,只要用年份标记就可以了。
ES6 的第一个版本,就这样在 2015 年 6 月发布了,正式名称就是《ECMAScript 2015 标准》(简称 ES2015)。2016 年 6 月,小幅修订的《ECMAScript 2016 标准》(简称 ES2016)如期发布,这个版本可以看作是 ES6.1 版,因为两者的差异非常小(只新增了数组实例的includes方法和指数运算符),基本上是同一个标准。根据计划,2017 年 6 月发布 ES2017 标准。
因此,ES6 既是一个历史名词,也是一个泛指,含义是 5.1 版以后的 JavaScript 的下一代标准,涵盖了 ES2015、ES2016、ES2017 等等,而 ES2015 则是正式名称,特指该年发布的正式版本的语言标准。本书中提到 ES6 的地方,一般是指 ES2015 标准,但有时也是泛指“下一代 JavaScript 语言”。
任何人都可以向标准委员会(又称 TC39 委员会)提案,要求修改语言标准。
一种新的语法从提案到变成正式标准,需要经历五个阶段。每个阶段的变动都需要由 TC39 委员会批准。
一个提案只要能进入 Stage 2,就差不多肯定会包括在以后的正式标准里面。ECMAScript 当前的所有提案,可以在 TC39 的官方网站查看。
Babel 是一个 JavaScript 编译器(compiler)。
主要用于在旧版浏览器或环境中将ECMAScript 2015+代码转换为向后兼容的JavaScript版本。
由ECMAScript 版本演进的介绍可知,JavaScript 语言的标准规范在每一年都会有所更新,在每个版本定版之前还会经历从提案到定案的不同阶段,不同的运行时(浏览器和 Node 等)、相同运行时的不同版本之间,对新的语言规范的实现情况都会不同,因此在使用新的语法特性的时候就显得狼狈不堪。Babel 就是为了解决这一问题,运用 Babel 编译器能够使得开发人员在写代码的时候完全不需要考虑运行时对新语法特性的支持程度,因为最终我们所写的ECMAScript 2015+的代码都会被 Babel转换为向后兼容(ECMAScript 2015或ECMAScript 2015之前版本)的代码。
Babel是一个编译器。编译代码的过程分为3个阶段:解析(parsing)、转换(transforming)、生成(generation)。
解析时,Babel 并不执行任何操作,就像const babel = code => code
一样,通过解析代码然后再次生成相同的代码。你需要为Babel添加一些插件来做一些事情(插件影响第二阶段的转换过程)。
对于 Babel 的编译过程,解析是基础,所有的语法转换的工作全部由插件完成,不同的插件负责不同的语法转换,比如plugin-transform-es2015-arrow-functions
这个插件就只负责对箭头函数语法的转换。因此, Babel 对于语法规范的不同版本的不同特性都是由这样的一个个插件完成的。
将一堆插件组合在一起,便构成了预设(preset)。官方给出的插件组合及预设有:
babel-preset-env
等同于 babel-preset-latest
或 babel-preset-es2015
+ babel-preset-es2016
+ babel-preset-es2017
和 ,对于一些老的项目可能还在使用 babel-preset-es2015
,现在可以考虑使用 babel-preset-env
进行替换。因此,现在最常见的预设配置为 env
+ stage-3
。 { "presets": [ "env","stage-3" ]}
许多其他社区维护的预设可在 上找到。
Babel 作为编译器,只对语法部分做转换,如:
[1,2,3].map(n => n + 1);转换为:
[1,2,3].map(function (n) { return n + 1;}
上述代码只转换了箭头函数语法,但对数组的新原生方法 map 却并未做任何改变。
为了支持新的原生对象和方法,需要使用babel-polyfill
。像 plugins 一样,也可以或选择性地包含所需要的内容。
由于ES5的限制,内置类(如Date,Array,DOM等)无法正确进行子类继承。
由于Babel假定代码将在ES5环境中运行,因此它会使用ES5函数。如果你当前使用的环境对ES5的支持有限或根本不支持(例如较低版本的IE),那么使用babel-polyfill将会增加对这些方法的支持。
如果你的类继承自另外一个类,那么静态属性是通过__proto__
继承的,这是最基本的认知,但是在10及10以下的 IE可能会遇到很多的问题。因为IE <= 10不支持__proto__,因此静态属性不会被继承。
IE8及8以下版本的 IE 不支持getters和setters,而且在IE8中,Object.defineProperty 只能用于DOM对象。
因此,如果你的代码计划支持IE8或更低版本,则不建议使用getter和setter,因为用了也没用。