成为浏览器通用运行时的 JavaScript

JavaScript 这门语言属于一系列 Web 标准套件中的一部分,这些标准定义出了可互操作的浏览器平台。它是仅有的一门网页开发者们可以预期在每个浏览器中都能使用79的语言。如 Java、Adobe Flash 和微软 Silverlightg 等其他语言环境,都不属于这个标准平台的一部分,必须使用特定于浏览器的扩展机制来集成到浏览器中——前提是这门语言支持这个浏览器。通常情况下,语言引擎必须由浏览器用户单独安装,并且可能无法完全集成到浏览器的标准服务中,比如基于 DOM 的图形模型。

浏览器博弈论预测,任何「通过增加另一种编程语言来扩展标准浏览器平台」的尝试,其成功的可能性都极低。浏览器厂商需要大量投入来设计、实现和推广一种新的 Web 语言,却不能保证它能在 Web 开发者中流行。要想让这门语言获得接纳,需要所有的主流浏览器都同意支持一种由竞争对手设计,且用户群很小甚至根本不存在的语言。而且这种语言还将成为长期的维护负担。例如在 2011 年,Google 推出了 Dartg 语言,将其作为一种更好的 Web 编程语言进行推广 [Krill 2011]。Google 分发了一个实验性的 Chromiumg 版本 [Google 2012a](这是他们的 Chrome 浏览器的开源基础),其中包含了一个 Dart 虚拟机 [Google 2012b],但这个虚拟机从未被纳入 Chrome 浏览器的生产版本或任何其他浏览器中。

随着 2005 年 AJAX 和 Web 2.0 风格应用的出现,Web 开发者开始编写更大型、更复杂的 Web 应用,其中有些人开始寻找一种能比 ES3 级的 JavaScript 更适合此类应用的编程语言。如果开发者需要编写的代码可以作为网页的一部分在任何浏览器中运行,而他们又需要或想要用 JavaScript 以外的语言编写代码,该怎么办呢?唯一的选择是使用 JavaScript 作为运行时支持的替代语言。这可能可以通过用 JavaScript 为替代语言编写一个解释器来实现。但在 2000 年代中期,JavaScript 引擎仍然是以相对较慢的解释器来实现的,而且 JavaScript 对编写高效解释器而言并不是种很好的语言。实现一个慢上加慢的解释器,显然并非有足够吸引力的解决方案。一种更可行的方法是通过「源对源翻译器」来承载替代语言,即经由编译器,将替代语言的源代码翻译成「可在浏览器 JavaScript 引擎上运行的 JavaScript 代码」。如果替代语言的语义和 JavaScript 的语义之间有合理而接近的匹配,那么用这种方式编译出的程序,其运行时性能就可以相对接近于手写的 JavaScript。

2006 年 5 月公开发布的 Google Web Toolkit(GWT)[Google 2006],是首个使用源到源翻译的 AJAX 工具套件。GWT 集成了 Java 到 JavaScript 的编译器,它被成功地应用于一些 Google 重要的对外 Web 应用,并在 Google 以外也得到了大量使用。GWT 的成功,证明了将 JavaScript 用于源对源翻译的可行性,许多其他语言的翻译器也随之而来。有一份文档记录了「可编译到 JS」的语言列表。它在 2011 年 1 月共有 19 个条目 [Ashkenas et al. 2011],但到 2018 年则包括了 270 多种语言 [Ashkenas 2018]。这些语言要么被翻译到 JavaScript,要么以 JavaScript 为宿主。这些语言中有一些是玩具级或不完整的实现,然而其中也有许多是拥有大量用户的严肃编译器,甚至还有一个针对 JavaScript 的 Dart 编译器。

源对源翻译不仅被用来支持 Web 页面上的遗留语言,还为实验新语言和扩展 JavaScript 提供了一种手段。CoffeeScriptg [Ashkenas 2010] 是最成功的源对源翻译器之一,它由 Jeremy Ashkenas 在 2009 年到 2010 年开发。在成为 Web 开发者前,Ashkenas 曾用 Ruby 语言编程,他更喜欢 Ruby 相对无标点符号的语法和 Python 式的留白缩进,而非 JavaScript 使用的 C 风格语法。他创建了 CoffeeScript 作为 JavaScript 的新表层语法,同时保留了 JavaScript 的底层运行时语义。Ashkenas [2009] 这样描述他在 CoffeeScript 上的工作:

长期以来,JavaScript 都把一个漂亮的对象模型隐藏在了 Java 式的语法中。CoffeeScript 试图通过偏重表达式而非语句的语法,减少标点符号的噪音,提供优雅的函数字面量,从而展示出 JavaScript 好的部分。像 square: x => x * x 这行 CoffeeScript,就可以编译成这样的 JavaScript:

  1. var square = function (x) {
  2. return x * x;
  3. };

除了「漂亮的函数」以外,CoffeeScript 还引入了许多提高编程便利性的语法糖,包括类声明和解构操作。这些内容都很容易转为 JavaScript 代码。许多 CoffeeScript 的特性与 ECMAScript Harmony 所考虑的特性类似。CoffeeScript 验证了 JavaScript 程序员对这些特性的兴趣。CoffeeScript 很快就变得相当流行,并被许多主要网站的开发者所采用。但在 ES2015 普及后,它的使用量就逐渐减少了。

在 2011 年 5 月的 JSConf 大会上,Brendan Eich 与 Jeremy Ashkenas 一起分享了 CoffeeScript 及其在 JavaScript 的 Harmony 演化中的作用。在他的演讲中,Eich [2011c] 介绍了一个名为「转译器g」的术语,用来描述像 CoffeeScript 这样的源对源编译器。这并非「转译器」一词首次出现,但在 Eich 的演讲前,这个词并未被广泛地了解和使用。后来,这个概念开始在 JavaScript 开发者社区内外被普遍使用。

Alon Zakai [2011] 的 Emscripten 是一个能将 C/C++ 翻译成高效 JavaScript 代码的转译器。它的诞生前提,在于作者发现通过 JavaScript 的 32 位算术编码模式和二进制的 TypedArray 数据结构,可以定义出一个易于被基于 JIT 的 JavaScript 引擎优化的 C 语言执行环境。Emscripten 启发了 asm.js [Herman et al. 2014],这是个定义了一组 JavaScript 代码模式的规范。相应编译器所生成的符合规范的 JavaScript 代码,都应该能被引擎识别和优化。asm.js 的成功进一步带来了 WebAssembly [Haas et al. 2017],它以字节码级接口扩展了 JS 引擎,可以作为 C/C++ 和类似的低级语言的编译目标。