JavaScript 统治的世界,烤面包机将能运行 JS 了

566 查看

从浏览器到手机,从平板电脑到桌面电脑,从工业自动化到最小的微控制器——最近JavaScript 似乎蔓延到了最意想不到的地方,不远的将来,你的烤面包机也将会运行 JavaScript ……。但是为什么?

事情之由来

最近几个月来,在匈牙利大大小小的会议上,经常有人要我谈谈下一步的 JavaScript(多半是 ES6——ECMAScript 6,以及 JavaScript-of-things——万物皆用 JavaScript)和 DOM 技术(也是就说 Service Workers)。

在其中的一次会议上,我得到了一个机会作关于“JavaScript 统治的世界”的讲演。现在回想起来,5 分钟的讲演时间有点儿紧张,既要讲一些 JavaScript 的历史,又要涉及多种多样的潜在应用,这些潜在应用正在蓄势待发,即将引爆 JS 生物圈——这就是当时的基本想法。

而且,为了抓住要点,在我讲完之后,有一位听众问了我这样的问题:

“为什么你没有提到 Netscape 最初的服务端 JavaScript 解决方案,却拿 node.js 来谈论‘一切由此开始’”?

嗯,(除了明显的时间限制之外)这是一个合理的问题——虽然我认为它对于“一切由此开始”的理解是错误的。我想这值得认真回答一下,而不是用下面的答案来应付。

“node 不是第一个服务端的 JavaScript 解决方案,但对于 JavaScript 今天的应用情况来说,它却是一个转折点。”

服务端 JavaScript 本身就是一个话题,而且也不是在 5 分钟之内就能够说清楚的——这里,我的意思是,node.js 开启了 JavaScript 的一个新时代——而这个新时代远远不只是服务端 JavaScript!且慢,让我们先沉住气……

偶然的 JavaScript

再回味 David Linden 的原著“The Accidental Mind”

要想弄懂 ECMAScript 标准中那些晦涩难懂的内容,就必须学一大堆东西——不光要学 JavaScript 的未来,还要(为了完全理解编辑的动机及其所受限制)学它的历史。我就是这样过来的,随着我将 JavaScript 的知识碎片慢慢地拼缀到一起,事情也就渐渐变得明朗了,这是一种有着很多“包袱”的语言——有着各种不宜见光的秘密。

JavaScript 是一个不成熟的产品,大部分人读到这里,都会意识到由其创立者 Brendan Eich 给出的关于 JavaScript 仓促发布的趣闻轶事。然而(这点很重要)这并非必然是件坏事情!

JavaScript 是一个不成熟的产品,就像我们的大脑一样——其进步更多的是通过演化,而不是革命。早期发展中积累的所有零零碎碎都充斥其间,却又承担不起丢弃这些零碎的代价——而同时,各种精致灵巧的附件不断涌现,遮掩了 JavaScript 早期的这种不足。

在我们沿着记忆的长廊开始 JS-演化的文明之旅之前,先来看看 JavaScript 的名词术语及其早期历史。

你可能早就熟悉了 JavaScript 这个名字——这名字一直就这么叫着,然而这不过是借 Java 之名而搞的一个市场营销的噱头,Netscape 的浏览器脚本语言最初叫 LiveScript。嘿,你要是花足够的时间来了解 Brendan Eich,可能还听说过他们曾想叫它“Mocha”。

当然,Netscape 命中注定成为内嵌客户端脚本的专有浏览器——微软的 IE 也配备了它自己的解释型脚本语言,JScript。JavaScript 和 Jscript 类似,但并不相同:接下来二者的兼容问题就开始折磨那些当代的“Web 设计师”。所以,Netscape,微软和欧洲计算机制造商协会(现为 Ecma 国际)就着手统一语言的语法,并对浏览器脚本进行标准化,正如你所猜测的,新的脚本语言称为 ECMAScript。

ECMAScript 2015 候选发布版 1

基于 ECMAScript 标准,后来又出现了若干种语言——最广为人知的可能就是 flash 的 ActionScript 了,ActionScript 试图弥补 JavaScript 的不足,然而,尽管做出了努力,但仍然没能将其扩展功能纳入主流。ActionScript 之外,以后不少语言都以各种方式扩展 JavaScript(像 CoffeeScript 及 TypeScript),而 JavaScript 自身也在不断演进,将一些最好的思想都纳入到了核心中。ECMA(准确地说是 TC39 工作组)也发布了基础标准的若干版本——ECMAScript 5.1是最新的(稳定的)版本,而 ECMAScript 2015——之前称为 Harmony,ES.Next,或直接称为第六版(ES6)——目前处于候选发布版 3 阶段,准备六月份由 ECMA 全体大会批准。

占领浏览器

本文不是为 ES6 的卖点做宣传——有很多相当好的资源可以了解那些即将面世的酷炫功能和扩展——不管怎么说,我认为我们对于 JavaScript 已经基本上统治了浏览器市场这个事实的认识应该是一致的。当今的浏览器中并没有太多的客户端语言,这是有原因的:VBScript 已经从 IE 11 中卸掉了,虽然谷歌仍试图将 Dart 硬塞进 web 中(嗯,大多是指 Chrome)——但并不很成功。

那留下来的是什么呢?不多,基本上是一些(或相当多)编译为 JS 的语言以及 JavaScript 的语言超集,但最终,即便是这些语言也只是用于推动 JavaScript 向前发展而已。JavaScript 甚至可以编译为自身——有工具可以将 ES6 的新功能转译为以前的标准版本,以便向后兼容。

突然,一个狂野的 JavaScript 出现了

随着集纳越来越多开发者的智慧分享,JavaScript 变得越来越普遍,开始出现在一些陌生的、出乎意料的地方:起初,这些地方只是 web 的天涯海角,纵使 JavaScript 广为流行,也仍然没有被触及到。随后不久,JavaScript 就无处不在了。本质上,IT 领域有两条(左右)广为人知的定律,为 JavaScript 统治世界做出了贡献,甚至预言了这一点。

摩尔定律

对于任何读到这里的人,我不认为还需要解释计算领域中最为基本的定律:(过度简化的)摩尔定律认为,硬件复杂性(从而速度)以加速的方式增长。迄今为止,这条定律已被各种方式通用化了(例如:“紧随微小的革命及范式转换而来的指数级进步”到底在讲些什么),但我们感兴趣的是其最原始的方面:体积不断缩小的同时,强悍的计算能力的爆炸式增长。

 


摩尔定律的解释

这些似乎停不下来的技术进步都带来了什么呢:它们使得在开发应用程序时,性能不再显得那么敏感。在一个软件生态系统中,性能只是一个方面——开发人员的智慧分享(程序员熟悉语言与环境)、可用的工具及库、开放性以及人员入职成本都是要考虑的非常现实的约束条件。与今天廉价但足够强大的设备比起来,在过去那些兆赫兹和千字节都非常稀有的日子,JavaScript 的这种非最优化的本性确实是一个很大的障碍。

摩尔定律使移动 web 变得可行——同时,JavaScript 突进到了服务端、设备及移动操作系统领域,最终将渗入极微小的微控制器中。

Netscape 最初的客户与服务端 JavaScript 架构

提供你的脚本,并吃掉它

正如上文提到的,node.js 并非 JavaScript 第一次出现在服务端——早在1994 年,就在该语言出现不久,Netscape 就在其企业服务器上提供了服务端 JavaScript 的执行环境。

但并没有坚持下去。

为什么?主要是因为在后端使用 JavaScript 确实令人头痛。举个例子,它根本不能即时更新,即使是最微不足道的修改,都要编译之后才能用。

当然,只是因为 Netscape 的系统不能胜任,并不必然意味着服务端 JavaScript 就注定失败,所以针对这个问题,又出现了多个解决方案。

Helma & Narwhal 就是这些项目中的一个。Helma——现在放弃了(或者说,转到了 Ringo.js)——运行 Mozilla 的基于 Java 的 JS-引擎 Rhino,而 Narwhal 理论上支持若干个不同的 JS-后端。这两个框架都找到了各自的用武之地——但都没有得到广泛应用——可能是由于它们的几个短板,在我们探讨这些问题之前,还有一个内容我们要提到,那就是 DOM。

DOM 支配 JavaScript 世界

微软和 Netscape 各自发布他们带有脚本的浏览器之后,就开始着手对脚本语言进行标准化——其努力即后来的ECMAScript。可是,web 浏览器(即使在当时)也并非一个空壳,一个漂亮的脚本控制台界面。Web 浏览器包含了一个完整的环境(也可以这样说,它们自己的操作系统),其中 JavaScript(/JScript)只是一小部分——每次需要访问浏览器的某些功能(像文档的 HTML 标记或结点的风格)时,都需要某个 API 来完成相关的操作。

IE 的 dHTML(摘自《Developing XML Solutions》一书)

Netscape Navigator 和 IE 都推出了他们各自的(当然,不兼容的)访问文档对象模型的 API,最终都进行了标准化,而标准化的成果就是 DOM 标准。DOM 被设想为提供绑定——独立于语言和平台——到浏览器(或广义地,环境)所提供的功能,并随着 ECMAScript 的发展而发展,这些发展会体现在 W3C 随 ECMA 之后所发布的标准中。

DOM 中的基本模式——James Padolsey 提供图片

最终,除了 DOM 之外,又出现了几个其他的功能及扩展,有些进入了标准化程序,被接受为跨浏览器的解决方案,有些仍然局限于单一的浏览器厂商或环境。直至今天,仍然在持续不断地努力工作(其中一个合作成果是 Service Worker 规范)。作为扩展 web 的手段,以使其跟上技术发展的速度,这种推陈出新的努力是受欢迎的。

上面讲的这些内容有关系吗?嗯,举个例子,二者经常混淆,所以澄清误解并没有坏处——但更重要的,是因为所有客户端的 JavaScript 解决方案在访问其环境时都有标准化的 API,而所有上述服务端的解决方案却互不兼容(例如访问服务器上的文件内容)。以前曾经有试图融合两种技术的尝试:一种不太可能的混合方案就是 Jaxer,Jaxer 运行一个相当完整的 Firefox 实例,从而在服务端有完全的浏览器 DOM。然而,即使 Jaxer 也有自己处理大量环境因素的独特方式。所以,要使这种混合方式能有实际的效果,还得等待 node.js 的出现。

进入node.js

哇,到目前为止,关于 node,我是鼓吹得太多了(但我无意推销给任何人),但愿我能公正地回答下面这个问题:

为什么 node.js 如此重要?

它正好在正确的时间出现在正确的地点。

浏览器大战其间,JavaScript 的速度提升

浏览器大战是激烈的,JavaScript 的执行速度已经 10 倍于我们此前见过的任何东西——而且由于激烈的竞争,其执行速度仍然在稳步地提升……然而,node.js 的创立者 Ryan Dahl 甚至都没有试过将 JavaScript 塞入服务器环境中。

相反,应当承认,他只是在研究事件驱动的服务器技术时,碰巧撞上了强大的 V8 引擎——JavaScript 正好适合于事件驱动的、非阻塞I/O的、基于单线程循环的环境。

异步

即便异步、非阻塞/事件驱动的 I/O 这样的想法根本不算新鲜,事件驱动的 web 服务器却也并不多。事件驱动的服务器要溯源到 1999 年,新生的 Flash(不,不是那个 flash)服务器就使用的这种技术——但其他使用这种模式的服务端解决方案很快就消失了。事实上,(如果我错了,请纠正我),比起我知道的唯一一个相关的解决方案——Tornado web 服务器(用 Python 写成),node.js 在时间上至少领先半年。

Alexandre 在评论里指出——Nginx 服务器也是事件驱动的(这实际上在 Ryan 最初的演示中就提到了,用来与 Apache的线程系统作对比),不过,我不认为这是一个公正的比较,因为 Nginx 事件驱动的功能太弱,也不太灵活。

也承几位读者指出,Twisted Framework(也是用 Python 写成)是一个事件驱动的异步网络框架,确实早于 node 的发布。Twisted 是不是适合上面的讨论,这一点可以争论,但无疑很有说服力——谢谢参与!

(对于上面提到的 Tornado web 服务器,Twisted 也具有丰富的互操作功能,但我认为 Twisted 更适合与 node 实现互操作,不过这只是个人看法。)

组织一个社区

作为服务端的 JavaScript 解决方案,组织一个相当规模的社区(有不同的层次),Node 是第一个。Node 作为开源软件发布之后,就有贡献者涌入了进来(某些人对贡献者的加入速度可能不太满意,拜托,这才刚开始呀),采纳 CommonJS 作为 node 的互操作平台(这里要感谢 npm),开发者的生态系统开始繁荣兴旺(Narwhal/RingoJS 二者都利用了 CommonJS 标准——但没有一个能取得像 node.js 和 npm 所带来的成功)。

2015年初 IO.js 的到来进一步扩大了 node 社区,并为社区注入了新的生命力

分支作者选择的开放管理模式开始发挥魔力,贡献者们蜂拥而至:短短几个月,活跃的贡献者数量就超过了早期的 node.js,而且本地化团队将社区推进到了新的疆界。

“好,好——node.js 给服务端编程注入了新的生命,它是我们的救世主,穿着闪亮盔甲的无畏勇士,是那啥和那啥——明白了,我的老天呐,烦死了……”——好吧,但它远不止这些!

扩展命令链

Node 使得将 JavaScript 以跨平台的方式嵌入……嗯,几乎任何事物中变得小菜一碟。从命令行开始,一眨眼的功夫,基于 JavaScript 的脚本和工具就迅速潜入了整个世界。

Node.js 及其衍生的大量工具使得跨平台任务自动化变得轻而易举,JavaScript 的扩展工具大量涌现,远远超出了以前的想象。

易于使用,可扩展,可用于所有相关平台,Grunt 和 Gulp 不仅围绕 JavaScript 和 web 来扩展工具,而是任何能想到的平台。(你知道连 Photoshop 都有自己内置的、可通过脚本访问的 node.js 实例吗?)

Jaxer 没能做成的(带有 DOM 的嵌入式浏览器),对 node 来说相当自然——首先借助 PhantomJS,其后不久,利用 JSDOM 中加入的 JavaScript 原生一类对象支持,在 node.js 中即可访问 DOM——而在一个类似的程序包中,重新实现了对 web 内容的自动化客户端测试(请查看 Domenic Denicola 关于 JSDOM 及其动机的精彩演讲!)你猜怎么着,继征服浏览器、服务器以及命令行之后,甚至还不满足——node 瞄准了鱼缸中的那条更大的鱼:桌面应用。

NW 的时代

托 JavaScript 的高速化以及 HTML5/CSS web 技术进步的福,HTML5 应用风靡一时——大多数时候这些应用毫不费力地跑在普通中低规格的设备上。node.js 带来了大量的访问底层(如文件系统和网络)操作系统原语的 API——将这些原语暴露给 web 渲染上下文实例简直小菜一碟——因为 Chromium 和 node.js 都是使用 V8 作为其 JavaScript 引擎的,将二者融合在一起就不是问题了:这就是 node-webkit(现在称为 NW.js)的由来。

Atom Editor 界面(全部以纯粹的 HTML5 写成),可以与任何当代原生代码编辑器相媲美

Node-webkit 激发出了一整套全新的桌面项目。在 Cloud9 IDE 成功整合 node.js 和 web 技术从而成为最先进的集成开发环境之后,像 Brackets 和 ATOM 这样的项目都将 web 体验带入了桌面(同时,两个世界各自最棒的部分——跨平台互操作性和扩展性——保持不变)。

更难,更好,更快,更强

由于 TC39 在 ECMAScript 标准化上的工作,JavaScript 越来越复杂,功能也越来越强大,其应用领域一直在增长,正变得无所不在(由于 node.js 以及其他模块的引入,其应用领域已远非过去能比),其演进并没有停下来的迹象,甚至都没有慢下来的迹象。

JavaScript 注定会越来越快,而其驱动的设备正在变得越来越小。

好,JavaScript——那么……

浏览器?没问题。服务器?容易!移动?拿下!桌面?尘埃落定!你还要尝试其他的吗?

那么,物联网怎么样?

万物皆用 JavaScript

万物皆用 JavaScript

有人可能会说,“物联网”的观念目前十分流行。智能手表,智能照明,智能取暖,智能房屋,联网的冰箱、洗衣机以及电热水壶——应有尽有。

这些都是小型的、低功耗的设备,小型的芯片加上有限的内存,再加上低功耗——像 JavaScript 这样的资源独占、耗电量大的语言,可能驱动不了这些设备,是这样么?

嗯,再想一想!万物皆用 JavaScript 的时代可能比你想象的来得要快。

火狐救援

几年以前,人们认为低功耗、成本敏感的设备根本不可能使用 web 技术——但 Firefox OS 证明了这不仅可行,而且最近两年里在大约 30 个国家发布了 15 种不同的基于 Firefox OS 的设备。

所有这些设备都是成熟的联网、触屏的智能手机——售价低到只有 33 美金,而且其他类型的装置(如松下的 FirefoxOS 驱动的电视)也证明了,JavaScript 和 web 技术仍然远未触顶。

实际上离所说的界限还远着呢,这不,三星决定将其用于它自己的运行 Tizen 操作系统的智能手表平台上——而同时,还与 Ecma 国际的 TC39 工作组进行接触,看能不能标准化一种用于移动的更加节俭的语言子集,以便用在更小尺寸的设备上。

我们目前还不知道三星的提议有什么结果——嵌入式 JavaScript 本身就是一个非常活跃的话题。三星最初的 TC39 宣传材料上引用了 Technical Machine 公司的 Tessel 以及 Espruino,这是两种使用 JS 的微控制器平台。

下面我们来看看如何为这样资源高度受限的硬件创建一个 JavaScript 的解决方案。在进入细节之前,我想先介绍一下第二个定律,与大家分享:

“任何能用 JavaScript 写的应用,最后都将会用 JavaScript 来写。”——由 Jeff Atwood 发明的 Atwood 定律

我就不用过度解释这个定律了(其实定律本身已经自我解释得很好了),但我会在下面展示其应用。

小世界的脚本

JavaScript 本质上是一种解释型语言,这意味着在执行之前要对源代码进行评估。当然,可以注意到有各种优化方法来减轻 JavaScript 的执行负担,提高速度——其中最基本的(而且也是最有效的),就是对源代码进行解析并建立 AST(抽象语法树)。AST 是源代码的一种表示形式,易于被解释器遍历。源代码并没有编译为机器码,而是即时执行的,从而避免了对源代码进行解析(以及再解析)和分词(tokenizing)的开销。这种方式没有多少缺点,好处却很明显——这就是为什么几乎所有 JavaScript 引擎都如此这般的原因。甚至有些分词器(tokenizer)就是用 JavaScript 写的(如 Esprima),这些分词器可以用来观察甚至操作这种中间状态(指 AST——译者注)。Esprima 的 AST 输出可以用于几种有趣的场景,如转译以及代码的自动完成(有许多用于 JavaScript 前处理及代码转换的工具都是基于 Esprima 的)。

这种方式有个小问题。除了初始处理的需求之外,还需要额外的内存空间用于存储 AST 以便后续执行。对于大多数应用来说,这并不是一个限制因素,但当我们走到低于兆级内存的微控制器时,这就是一个非常实际的限制了。

Espruino 的下一代,Pico——计划在 2015 年 5 月份发布

Espruino

上面的问题也正是第一代 Espruino 的创建者 Gordon Williams 所面对的问题——小型的可编程 IC 包含一个控制器和 64 K(!)内存,但仍然执行 JavaScript。编译或解析为 AST,对于这样的低内存设备并不可行(标准的某些要求不得不蒙混过去,像 Unicode 支持,受限于内存容量,无法实现)。因为这个原因,Espruino 上是直接执行源代码的,边解析边执行!当然,这样一来,就会有相当多的怪异之处要考虑——譬如空格就会对 Espruino 上代码的运行速度产生影响!

即将发布的新一代 Espruino,Espruino Pico,会有一点儿自由支配的内存(96KB),但即使这样,仍然不足以满意地解决问题。由于这些原因(代码规模和兼容性),Espruino 板可以通过熟悉的 JavaScript 语言来编程,从而可以动态执行(即时解释),但不完全符合标准的引擎却使其无法充分利用语言所提供的丰富的生态系统。然而 Espruino 的强项,在于它的经济性,以及保证软硬件持续连接的能力(进一步降低了使用 Web IDE 这样的工具操控硬件的门槛)。

第一代 Tessel(取自 Technical Machine 的 Instagram)

第一代 Tessel

由于以上所述的这些原因,Technical Machine 公司开发 Tessel 的高手们选择了一条不同的路径。Tessel 有丰裕的内存(32 MB)由 JavaScript 解释器支配——然而驱动整个机器的 Cortex M3 微控制器才有大约 200KB 的内存,致使在上面运行任何高级 JavaScript 解释器都非常困难。

不过,有一种语言,从一开始就要求嵌入设备中,这就是 Lua。Lua 是一种非常类似 JavaScript 的程序设计语言,所以,写一个编译器将 JavaScript 源代码翻译为 Lua,然后在异常紧凑的 Lua 虚拟机上执行翻译的代码(Lua 虚拟机位于设备的微处理器中),简直神了!

因为这款 Tessel 设计为“node 兼容”,意思是能够没有多大困难地在 npm 之外直接运行大部分模块(只要这些模块之间没有任何二进制依赖——但那是另外一个问题了)。

上面所述的概念有时候行得通,有时候行不通,但大多数情况还是可以的——Tessel 的强项并非其 JavaScript 的兼容性或性能:而是其即插即用的本性。可以从网站订购预制的扩展插件,然后只需在 npm 安装命令行上给出插件的标识符,这些插件就可以在 5 秒钟之内通过简单的 JavaScript 调用(回调)函数进行访问了。

这种可扩展性和模块化样例展示了可扩展 Web 宣言中列出的途径——“向开发者暴露底层原语,让开发者在 JavaScript 代码中基于这些原语进行扩展”,Tessel 社区的模块目录完美地演示了这种基本观念。

现在,在继续考察 Tessel 的下一代产品之前,我想我们先绕道去 FirefoxOS 的地盘上去看看:

Firefox OS 怪异的双胞胎兄弟:Jan OS

Jan OS 是一头怪兽,由疯狂的荷兰人,阿姆斯特丹的科学怪人 Jan Jongboom 所发明——它既非一个手机平台(不再是),也非微控制器平台(还不是)——而是两者皆有那么一点。

Jan OS 基于 Firefox OS 的开放源代码(是其一个分支,如果你愿意这么说的话),专注于硬件和传感器。这些传感器和模块,你要作为 Tessel 附件买的话,要几百美元,焊接到一个非常紧凑且低功耗的 IT 板子上(几十美元就可以买到,过去称之为电话)。

所有这些传感器和电路要装在一张信用卡大小的 IC 板上!

Firefox OS(极简)的硬件 API 都在这儿了,剥掉不需要的琐屑的东西,UI(GAIA)以及安全/许可机制(主要用来支持电话),就得到了一个高度优化的,连接蜂窝网络的,低功耗的物联网的板子。如有足够的电力,你就能够,譬如说,带着这个奇妙的装置,在野外过上整整一个月,拍照并通过无线网络上传照片。

其背后原理,主要是运行了一个精简的、为移动优化过的 Linux 内核(Firefox OS 的内核,Gonk,是基于 AOSP 内核的),带 Mozilla 的 Gecko 引擎(包括内置的 SpiderMonkey JavaScript 引擎——全优化的,具备所有移动或桌面浏览器上所有花哨功能的——哈喽呜~~~,Jaxer!),但要有额外的 WebAPI,才能访问移动芯片组、wifi、FM 广播(!)、发送短信、打电话或捕捉陀螺仪数据!

我是不是还应该加上,(Firefox OS 及 Jan OS)甚至也支持 Raspberry Pi 呢?这样一来,在讨论 Tessel 2 之前,我为什么选择介绍 Jan OS,就非常明白了。

Tessel 2——裸机原型

两个 Tessel 连接到一个 Tessel 2 的 USB 端口上

下一代的 Tessel 将自己定位于上面提到的所有那些硬件优点的集合体:像 Raspberry Pi 那样强大,比第一代的 Tessel 便宜很多(价格低一半多!),像第一代那样模块化及可扩展(嘿,它竟然包含了两个标准的 USB 端口!)——最后但并非最不重要的,是运行小型的 Linux 内核以及 io.js,充分利用 node/io.js 所提供的强大功能、速度以及生态系统,一劳永逸地消除兼容性问题。

这已经很好了——但最好的还没到——你可以用 Tessel 2 建立产品原型,直接拿到市场上去——因为,假如你想要,就可以订购一批预先组装好的 Tessel 2,板子上有集成好的模块,然后再将其纳入你最终的产品中。

“分形”式的概念图

Technical Machine 的“分形”概念图使得上述场景更加诱人——通过 JavaScript 驱动的、贴近底层的性能敏感的编译后模块之间的无缝交互,将模块化和原型开发推进到了一个全新的水平。

你说性能敏感?贴近底层?让我们深入虎穴,去看看未来的 JavaScript 给我们提供些什么……

超越于时代:未来的 JavaScript 机器人

 

ASM.js 并不是新技术。——它甚至都称不上是一种技术,只是一串迭代优化,碰巧最后很容易被优化,而且运行确实比较快——如果你愿意,也可以说它更像一种制导的演进(guided evolution)。某些优化(像 AST)已经在前面提过了,这些优化有利于 JavaScript 的性能提升。AST 用于提升解释器的速度,从而使得运行时的执行更快——但没有人认为这是执行 JavaScript 的唯一方式。

这里的关键词是:编译。

自从早期的解释语言以来,即时(JIT)编译已广为人知,且广泛应用——JavaScript 也不例外(自然,对于 JavaScript 这样的动态类型语言来说,JIT有着一些怪异的行为和陷阱)。

Firefox 中 SpiderMonkey JIT 结构

优化热点代码(像长循环或频繁调用的函数),将其直接编译为机器指令会使执行性能大幅提升。ASM.js 试图要做的就是,通过定义一种中间形式的 JavaScript 子集语法,在执行前将 JS 源代码进行 AOT(事先)编译。编译生成类型安全的、可直接执行的机器代码,消除了托管内存和垃圾回收,因而性能就变得可以预测了。

即将加入 JavaScript——对原生 SIMD 指令的支持

对 ASM.js 来说,还有一个第二位的,不那么重要的目标——将 JavaScript 建立为一种快速的目标语言。编译为 JS 的工具,像 Emscripten 或 GWT,在 ASM.js 出现之前已经有一段时间了,由于将底层语言编译为 JavaScript 的习惯使然,对于这些编译器生成的源代码,引擎开始做优化。ASM.js 自然延续了这种做法(正像你注意到的,我努力不去过度使用“演进”这个词),定义一种通用的“语言”(一种标准语法格式),使得这些工具输出的源代码可以进行比较,易于优化,或许还有那么点儿可读性(当然,不可能照顾到每个人的习惯)。

妈妈你看,时光机!

结果?自然是令人心生敬畏——不论你说的是只要一个浏览器就可以运行的成千上万的 MS-DOS 游戏(多亏 archive.org 收集的游戏库),还是内置 HTML5(WebGL/JavaScript/ASM.js)的当代工具生成的令人惊奇的3D 图形(像 Unreal 引擎或 Unity 所生成的):

视频:https://youtu.be/2v6iLpY7j5M

GDC2015 会议上演示的 Unity 5 的 HTML5 导出结果

随着浏览器支持的不断扩大,这些令人惊奇的效果正在惠及越来越多的 web 用户,将高性能的游戏甚至物理模拟带到浏览器上,以及开放的 web 上。

然而,速度并不是唯一的考量,有时候规模(见上面 Espruino 的部分)也是同等重要的(如果不是更加重要的话)。

这就需要我们的小型脚本引擎出场了。

TinyScript——相对于 JavaScript 这个巨人的少年

前面已提过,Lua 经常被用来嵌入到其他软件产品中,以提供可扩展性及对宿主软件的脚本编程。这部分是由于 Lua 的简单和可扩展性,但也是因为它代码量少及内存占用量小的缘故。

对于 JavaScript 而言,很不幸(同样的简单,易于扩展,且相当出名)——其运行时的大小总是一个问题,所以尚没有得到广泛采用。基于我们对 Espruino 引擎的了解和设想,我们觉得这样说是理所应当的:“一个高速且符合标准的 JavaScript 解释器无法挤进这些应用要求的如此小的空间里”——但又一次,我们错了。

时间快进到 2015 年,muJS 和 Duktape 出场了——两个小型的 JavaScript 解释器,只为上述目的而做!它们以大约 200 KB 编译后的规模亮相,轻型且易于嵌入到任何产品中(就此而言,是微控制器!):Duktape 引擎能够挤入这样的一台小型嵌入式系统中,该系统带有 256 KB 的闪存,以及低至 96 KB 的内存,而且跑起来轻松愉快!

(另一方面,Espruino(甚至 Pico)的硬件,即使对这么小的运行时来说,也证明仍然太瘦了。)

使用 Duktape 做嵌入式脚本编程的原子游戏引擎(Atomic Game Engine)

夸耀符合 ECMAScript 标准以及期望现代 JS 引擎所应该具有的花哨功能(垃圾收集,Unicode 支持,RegEx 引擎——随便说),这些小巨人在工程上可不是那么简单。除了这些相当新潮的项目之外,已经有人开始在自己的项目中整合它们了:譬如,很长时间以来,游戏引擎就经常用 Lua 来满足对内置脚本的需求——另外,新的原子游戏引擎采用的是 Duktape。

Duktape 也用在裸机项目上,如物联网框架“AllJoyn.js”,而三星的 Tilmann Scheller 对该引擎在嵌入式系统上的通用性进行了评估(结果是大有希望)。

结语?

到此,我们关于 JavaScript 当前发展方向的匆匆一览就结束了——那么未来会怎么样,未来会如何发展?很难猜测,因为景象日新月异。

有一件事情应该是清楚的:JavaScript 不会很快消亡——所以最好学一点 JavaScript 的东西,以防万一。

事实上……

确实还有一件事情。看到人们在 web 上高声喊着陈旧的口号(甚至那些读完本文的人也是如此):

“JavaScript 仍然其丑无比,不堪卒用

这是语言的耻辱,且必终将如此!”

对于这些人:不用担心,用不了几年,JavaScript 终将灭绝——像渡渡鸟一样死去!

什……什么?!

在我们讲了这么多之后,到底是为什么还有人要这么说?前面的话本义上是“JavaScript 不会很快消亡”——这该如何解释?看起来我是自相矛盾了——实在抱歉,你应该去看一看 Gary Bernhardt 预言般的讲演,以便找出到底是怎么回事:

JavaScript 的生与死

提示:前面链接的 Steven Wittens 关于 ASM.js 的文章中也有一些建议。

*事实上 V8 的“Crankshaft”优化编译器随着代码规模的增加,也有类似的怪异之处,因为它选择哪些函数作为内联函数是根据函数代码文本的大小来决定的(而且空格和注释也包括在内!)。

想了解更多的话,请在 dotJS 2014 上查看 Joe McCann 的讲演。