什么是 JavaScript 引擎?

编写网络代码有时会让人感觉有些神奇,因为我们在文件中写入一系列字符,在浏览器中打开该文件,然后看着它生动地呈现。但了解这种神奇背后的技术可以帮助您更好地磨练自己作为程序员的技能。

在本文中,您将了解浏览器所采用的 JavaScript 引擎的复杂性,从而了解 JavaScript 驱动的 Web 或移动堆栈的幕后情况。让我们分析一下 JavaScript 引擎的作用、不同平台使用不同引擎的原因、这些年来它们的发展历程,以及我们作为开发人员为何应该关注这些引擎。

首先,一些术语

“JavaScript 引擎”通常被称为一种虚拟机。“虚拟机”是指由软件驱动的给定计算机系统的模拟。虚拟机有很多种类型,它们根据模拟或替代实际物理机器的精确程度进行分类。

例如,“系统虚拟机”提供了可执行操作系统的平台的完整模拟。Mac 用户可能熟悉 Parallels,这是一种系统虚拟机,可让您在 Mac 上运行 Windows。

另一方面,“进程虚拟机”功能不够全面,只能运行一个程序或进程。Wine 是一个进程虚拟机,它允许您在 Linux 机器上运行 Windows 应用程序,但不会在 Linux 机器上提供整个 Windows 操作系统。

JavaScript 引擎是一种专门用于解释和执行 JavaScript 代码的进程虚拟机。

注意:区分浏览器的引擎(通过布局网页)与解释和执行代码的低级 JavaScript 引擎非常重要。本文对浏览器的工作原理进行了很好的解释。

那么 JavaScript 引擎到底是什么?它有什么作用?

总的来说,JavaScript 引擎的基本工作就是将开发人员编写的 JavaScript 代码转换为快速、优化的代码,以便浏览器可以解释,甚至可以嵌入到应用程序中。事实上,JavaScriptCore 自称是一个“优化虚拟机”。

更准确地说,每个 JavaScript 引擎都实现了 ECMAScript 的一个版本,JavaScript 是 ECMAScript 的一个方言。随着 ECMAScript 的发展,JavaScript 引擎也在不断发展。之所以有这么多不同的引擎,是因为每个引擎都设计用于不同的 Web 浏览器、无头浏览器或 Node.js 等运行时。

您可能对 Web 浏览器很熟悉,但无头浏览器是什么?它是一种没有图形用户界面的 Web 浏览器。它们对于针对您的 Web 产品运行自动化测试非常有用。从 Chrome 59 版和 Firefox 56 版开始,常规浏览器可以以这种方式使用,尤其是用于测试。Node.js 在其中扮演什么角色?Node.js 是一个异步的、事件驱动的框架,允许您在服务器端使用 JavaScript。由于它们是 JavaScript 驱动的工具,因此它们由 JavaScript 引擎提供支持。

鉴于上述虚拟机的定义,将 JavaScript 引擎称为进程虚拟机是有意义的,因为它的唯一目的是读取和编译 JavaScript 代码。这并不意味着它是一个简单的引擎。例如,JavaScriptCore 有六个“构建块”,用于分析、解释、优化和垃圾收集 JavaScript 代码。

这是如何运作的?

这取决于引擎。我们来考虑两个重要的引擎:WebKit 的 JavaScriptCore 和 Google 的 V8 引擎。这两个引擎处理代码的方式不同。

JavaScriptCore 执行一系列步骤来解释和优化脚本:

它执行词汇分析,将源分解为一系列标记或具有已识别含义的字符串。

然后,解析器分析标记的语法,并将其构建到语法树中。

然后四个 JIT(即时)进程启动,分析并执行解析器生成的字节码。

简单来说,这个 JavaScript 引擎获取你的源代码,将其分解为字符串(即对其进行词法分析),获取这些字符串并将它们转换为编译器可以理解的字节码,然后执行它。

Google 的 V8 引擎以 C++ 编写,还可以编译和执行 JavaScript 源代码、处理内存分配和垃圾回收。其设计包括一个编译器管道,可将源代码直接编译为机器代码:

  • Ignition,生成字节码的解释器
  • TurboFan,一个将字节码编译为机器码的优化编译器
  • SparkPlug,一个补充 TurboFan 的编译器
  • 如果您对历史感兴趣,这个新的管道取代了 V8 以前使用的旧版 Full-codegen 和 Crankshaft 双编译器设计。

    一旦编译过程生成机器代码,引擎就会将 ECMA 标准中指定的所有数据类型、运算符、对象和函数公开给浏览器或任何需要使用它们的运行时,例如 Node.js、Deno 或 Electron(由 Visual Studio Code 使用)。

    绕道一圈:运行时

    如果 JavaScript 引擎在后台悄悄运行,解析代码并将其分解为可读字符串,以便编译器可以读取和编译它,那么运行时往往会吸引更多关注。这是为什么呢?

    众所周知的运行时在 JavaScript 引擎之上运行,从而扩展了它们的功能。最著名的是 Node,但 Deno 和 Bun 是该领域的新手。Node 和 Deno 嵌入了 V8,而 Bun 嵌入了 JavaScriptCore。

    Bun 声称运行速度比 Node 或 Deno 更快,因为 JavaScriptCore 比 V8 更快,每秒处理 69,845 个 http 请求,而 Node 为 16,288 个,Deno 为 12,926 个。

    Bun 的文档指出,这些运行时的目标是“在浏览器之外运行世界上大多数 JavaScript,为您未来的基础架构带来性能和复杂性增强,并通过更好、更简单的工具提高开发人员的工作效率。”事实上,这些运行时利用了 JavaScript 引擎的强大功能,使 JavaScript 在浏览器之外运行。

    NativeScript 是专为使用 JavaScript 构建的跨平台原生移动应用程序开发而构建的运行时的一个很好的例子。

    这些运行时也是为了解决 JavaScript 单线程架构带来的一些固有问题而构建的。例如,Node 优先考虑异步、无线程的例程执行。所有这些运行时都提供了精心策划的开发人员体验,包括内置对深受喜爱的 API 的支持,例如 fetch、websocket,甚至是 React 开发人员喜爱的 JSX。这可能是它们往往吸引开发人员注意力的原因。

    总体而言,运行时解决了标准浏览器架构及其驱动引擎在性能方面的明显差距。随着引擎的发展,这些运行时也必将不断发展。

    有哪些 JavaScript 引擎?

    有多种 JavaScript 引擎可用于分析、解析和执行客户端代码。随着每个浏览器版本的发布,JavaScript 引擎可能会进行更改或优化,以跟上 JavaScript 代码执行的最新水平。

    在被这些引擎的名称弄得晕头转向之前,记住这些引擎和它们所依赖的浏览器都经过了大量的营销推广是很有用的。在这篇对 JavaScript 编译的有用分析中,作者讽刺地指出:“如果您不知道,编译器大约有 37% 是由营销构成的,而从营销角度来说,重新命名是您可以对编译器进行的少数几件事之一,因此有了以下名称:SquirrelFish、Nitro、SFX...”

    在牢记这些引擎的命名和重命名的兴衰的同时,了解一下 JavaScript 引擎历史上的一些重大事件也是很有用的。我为您编制了一个方便的图表:

    *JavaScriptCore 被重写为 SquirrelFish,更名为 SquirrelFish Extreme,也称为 Nitro。不过,将 JavaScriptCore 称为 WebKit 实现(例如 Safari)背后的 JavaScript 引擎仍然是正确的说法。

    **Edge 最初使用的是 Chakra 引擎,微软已将部分引擎开源。Edge 随后被重建为 Chromium 浏览器,并采用 Blink 和 V8 JavaScript 引擎。

    我们为什么要关心?

    JavaScript引擎的代码解析和执行过程的目标是在最短的时间内生成最优化的代码。

    归根结底,这些引擎的演进与我们追求网络和移动环境的演进相一致,以使其性能尽可能高。为了跟踪这一演进,您可以在基准测试图表(例如 arewefastyet.com 上生成的图表)中查看各种引擎的性能。

    任何 Web 开发人员都需要了解浏览器固有的差异,这些浏览器会显示我们辛苦编写、调试和维护的代码。为什么某些脚本在某个浏览器上运行缓慢,而在另一个浏览器上运行得更快?

    同样,移动开发人员,尤其是那些使用 webview 来显示内容的混合移动应用程序的开发人员,会想知道哪些引擎在解释他们的 JavaScript 代码。所有关心用户体验的 Web 开发人员都应该了解小型设备上各种浏览器固有的局限性和提供的可能性。跟上

    随着您成为 Web、移动或应用程序开发人员,JavaScript 引擎将会是您值得花费的时间。

    本文最初于 2015 年在 Telerik 开发者网络上发布,此后经过修订和更新,以适应 2022 年及以后的需要。原始文章在维基百科的 JavaScript 引擎条目中被引用。