这一章介绍在当前的JavaScript环境下开发ECMAScript 6你需要的操作。以下涵盖的都是备选的相关工具。如果你想要一个综合的工具清单,我建议你去Addy Osmani的“ECMAScript 6 Tools”看看。
在现在使用ECMAScript你有什么样的选择?
- 当前的JavaScript引擎(浏览器,Node.js,...)已经支持大部分的ES6。你可以通过以下方式来查看已支持哪些特性:
- Kangax的“ECMAScript 6 compatibility table”。
- William Kapke的“Node.js ES2015 Support”列出了在Kangax的表格中仅与Node相关的部分。
- ES6 REPLs(交互式命令行)可以让你测试出小部分的代码。在本章中的一个部分列出了可用的选项。
- 把ES6编译成ES5:特别声明,如果你需要考虑对历史遗留的引擎作支持,在相当长的一段时间里,将ES6编译成ES5是唯一可选操作。将源码编译成源码也可以称之为源码翻译。你可以在部署环境(静态地)和运行时(动态地)用源码翻译的方式来使用ES6。这一章会阐述如何让它运转,以及其他的既有ES6工具和库。
ES6是ES5的超集是一个可喜点,这意味着你所有的基于ES5的代码都是在ES6中生效的。因为你可以逐渐的去使用它,这也帮助你更快接受专属ES6的特性。
市面上有很多REPLs(命令行)用来交互地与ES6互动。以下项目提供丰富的选择为在线编码提供互动的玩乐场所:
另外,Babel通过它的babel-node工具将对ES6特性的支持带到Node.js。
有三个必要的选择你需要为代码翻译工具准备:
- 一个代码翻译器(为你的代码服务)
- 一个包管理工具(用来安装已有的库)
- 一个模块系统 (为了完整的应用)
需要注意的是这些选择都不全是独立存在的;不是每一个模块系统都可以和每一个其它包管理工具一起运作。下一章详细介绍这些选择中的每一项。
一个翻译器能将ES6的代码翻译成ES5。有名的选择有:
- Microsoft TypeScript:是一个基础的ECMAScript6+可配置的类型注解。
- Google Traceur:是最具人气的ES6翻译器。是法语发音,/tʁa.sœʁ/;用英语的话接近地发音成“truh-SIR” (来源, 听法语母语的人对这个单词的发音)。
- Babel:是一个更新的,事实上即将成为标准的ES6翻译器。Babel除了支持ES6以外,还支持React的JSX语法。发音为“babble”(想象一下澳大利亚的重音 - Babel的创造者,澳大利亚的Sebastian McKenzie)。
- Closure Compiler:如果你用接下来的两个命令行选项,就能够当作例如ECMAScript6至ECMAScript5的静态翻译:
- 指定输入语言:
--language ECMASCRIPT6_STRICT
- 指定输出语言:
--language_out ECMASCRIPT5
原则上,翻译器以下面任意一种方式完成翻译:
- 静态地(部署环境之前)或者
- 动态地(在运行时)
作为一个构建步骤,TypeScript,Traceur,Babel和Closure Compiler让你在以下格式化模块中提供ES5代码。你可以任意直接得调用他们或者用一个构建工具(grunt,gulp,broccoli,或其它)。
- AMD
- CommonJS
- ES6 module loader API:使用这个API会通过polyfill把ES6代码翻译成ES5。
在浏览器上,很多ES5模块都是通过等会儿介绍的模块系统之一来加载。在Node.js上,你可以用内置的模块系统(也存在其他的选择,比如webpack和ES6模块加载器polyfill)。
在浏览器上,你通过一个库加上一个定制的<script type="...">
动态编译。这个操作在Traceur和Babel上存在。
在Node.js上,Babel有工具可以进行在传输过程中的编译。这些会在另一个章节描述。
你需要一个包管理工具来安装第三方库。这是三个有名的:
- npm:Node.js的与生俱来创建的包管理工具,在客户端开发中的受欢迎程度多亏像browserify和webpack这样的模块打包和加载工具得以持续增长。包含CommonJS模块的包和/或比如命令行工具的其它内容。
- Bower:客户端代码的包管理工具。最知名的包是AMD模块,但是CommonJS模块,CSS,HTML和其它物件也能通过它管理。
- jspm:SystemJS的包管理工具(看下一个bullet list)。它能通过多样的来源安装模块,包括Github和npm。jspm的一个关键特性是外部模块能用ES6书写(会被代码翻译)。不仅是你自己的模块。
模块系统让浏览器支持模块化(Node.js有一个内置模块系统)。那样,你可以通过模块系统构建出你自己的应用和库模块。有名的模块系统有:
- RequireJS:是一个AMD模块的加载器,它可以静态地通过TypeScript,Traceur,Babel和Closure Compiler创建。模块插件(基于Traceur和Babel)让它可以加载ES6模块。
- Browserify:打包CommonJS模块(包括通过npm安装的那些模块)用以让它们通过浏览器加载。通过基于Traceur和Babel的转换器(插件)支持ES6模块。
- webpack:一个CommonJS模块(包括通过npm安装的那些模块)或AMD模块(包括通过Bower安装的那些模块)都可以用的打包和加载工具。通过定制基于Traceur和Babel的加载器(插件)以支持ES6模块。
- SystemJS:一个支持ES6模块及用CommonJS,AMD和“ES6 module loader API”格式书写的ES5模块,基于ES6模块加载器Polyfill的模块系统。
Linters和检查器静态地分析源码并反馈与风格,方式等相关的问题。
下面的Linters都支持ES6,但是取自不同的角度:
下面的检查器都支持ES6:
- Flow:会被类型检查的代码。它以以下三个来源获得类型信息:
- 类型注解语法(非规范的)
- 在注释中做类型注解
- 类型接口(通过静态地统计源码进行类型推断,甚至完全没有注解的代码Flow都是有效的)
- TypeScript:除了可以作为一个代码翻译器,TypeScript的编译器和也与Flow的运作方式类似并且对类型问题作警告。
- Closure Compiler:会被类型检查的代码并且能理解存在JSDoc标记中的类型信息。
很多测试工具(比如Jasmine和mocha)能被以最多得使用因为他们能与被代码翻译后的代码一起运作,所以他们没有必要去理解ES6的原始代码。Babel的文档中有关于如何去与非常多测试工具一起使用的信息。
Shims/polyfills用ES5代码让你可以使用满足ECMAScript6规范的库:
这里有一些可以处理ES6的句法分析器:
- Esprima
- Acorn
- Babel最近已经成为不只是一个能静态分析和转换JavaScript源码的框架了。“Babel Plugin Handbook”(来自James Kyle)提供了部分关于Babel为什么能够运行的信息。
这里有一些可以理解ES6的文档工具:
当第一个引擎完全支持ES6,直到所有不支持ES6的引擎淘汰,一个hybrid的实现可以被用在客户端的应用上:
- 每个文件在服务器上都有两个版本:本地化ES6版本以及其代码翻译版,一个ES5版本。
- 当应用启动时,特性侦查会习惯于检查ES6是否被完全支持。如果有,使用ES6版本的应用。反之,使用ES5的版本。
查清ECMAScript的版本是很难的,因为很多引擎在完全支持前会支持不同的版本的一些部分。比如说,这是你想要检查一个引擎是否支持ECMAScript6的for-of循环 - 但那能很好地确定引擎对这一个ES6特性的支持:
function isForOfSupported() {
try {
eval("for (var e of ['a']) {}");
return true;
} catch (e) {
// 有可能性地:检查e是否是SyntaxError的实例
}
return false;
}
Kyle Simpson的ES Feature Tests让你可以侦查一个引擎是否支持ES6。
var ReflectSupports = require("es-feature-tests");
ReflectSupports("all", function (results) {
if (results.letConst && results.arrow) {
// 本地化使用ES6
} else {
// 使用代码翻译的ES6
}
});
npm也许最终会支持同一个模块的两个版本,基于npm,你将能同时用Node.js和客户端模块程序的ES5和ES6版本发布库。
有些ECMAScript6的特性不能代码翻译(编译)成ES5。ES6有三种类型的特性:
- 已存在特性的更好语法表达
- 标准程序库中的新功能
- 全新的特性
下一节解释把这每一个特性用代码编译成ES5有多困难。
ES6已经可以通过库使其提供的更好语法特性可用。两个例子:
- 类
- 模块
都可以相对简单地编译成ES5。
ES6有一个比ES5更广泛的标准。添加的内容包括:
- 字符串,数组的新方法
- Promises
- Maps,Sets
这些都能通过一个库来提供。大部分的功能(比如String.prototype.repeat()
)对ES5来说简直有用。稍后一点的章节会列出一些这样的库。
ES6有一些与已存在的特性无关的特性。这些特性永远不可能如实得被代码翻译。不过它们中的一些可以有原因,比如: But some of them have reasonable simulations, for example:
let
和const
:被代码翻译为var
并且必要的时候会将定义重命名来避免命名重复。这能进行快速的代码开发并且在实践中也能很好的运作,但是你不会得到常量在本地化ES6中所拥有的绑定不变性。- Symbols:被代码翻译为唯一的ID标识的对象。这些对象可以被用作属性的键,因为括号运算符可以将它们强转成字符串。此外,一些有遍历属性能力的方法(比如
Object.keys()
)只能打上补丁来忽略那些作为键的被代码翻译的symbol。 - Generators:被编译成状态机,是一个复杂的转换,却能够简直良好地运作。比如,这个generator方法:
function* gen() {
for(let i=0; i < 3; i++) {
yield i;
}
}
被代码翻译成以下的ES5代码:
var marked0$0 = [gen].map(regeneratorRuntime.mark);
function gen() {
var i;
return regeneratorRuntime.wrap(function gen$(context$1$0) {
while (1) switch (context$1$0.prev = context$1$0.next) {
case 0:
i = 0;
case 1:
if (!(i < 3)) {
context$1$0.next = 7;
break;
}
context$1$0.next = 4;
return i;
case 4:
i++;
context$1$0.next = 1;
break;
case 7:
case "end":
return context$1$0.stop();
}
}, marked0$0[0], this);
}
你可以看到代码中的状态机,下个状态被存储在了context$1$0.next
中。
看看Facebook的regenerator库获取更多信息。
- WeakMaps:整个WeakMap里都是键值对。键基本都是对象。WeakMap的最主要的特性就是键是弱连接:如果将一个对象作为键并且没有其他与它相关的(强)引用的话它是可以被垃圾回收的。一种使用ES5对WeakMap的模仿是一个对象包含一个唯一的ID(比如一个
'weakmap_072c-4328-af75'
的字符串),否则就是空的。它的每一个键值入口都被以值存在键上,通过一个属性名为WeakMap的ID管理起来。这确实相当偏门:这只会在键名可变时生效。再者,如果一个仿制的WeakMap被垃圾回收,它的那些在键中的属性没有移除,结果就是浪费内存。从好的层面来说,键都被仿制的WeakMap弱持有了。仿品能运作因为WeakMap的操作(get
,set
,has
,delete
)都需要一个键。没有任何办法清除WeakMap或者去遍历它们的入口,键和值。
其他完全没可能用代码翻译的特性(我可是用了直接了当的语气):
- Proxies:只有你让所有对象上的操作可截取,截取代理操作是唯一可能办法。这会导致一个极大的展现错误。
- 内置子类的构造函数(比如
Error
和Array
)。通过一个新的ES5内置中不支持的子类协议可以让这些特性生效(new.target
等)。用一个实现来代替它们真的不会是一个简单的解决方案,因为有很多地方都会用到内置构造函数,不要通过全局变量去涉及它。 - 调用跟踪优化:普遍的实现调用跟踪优化在ES5里需要一个很激进得对ES6代码的转换(比如trampolining)。转换出来的结果代码是非常慢的。
首页:架设ES6
上一章:关于本书