本文档解释了 JavaScript 引擎如何解析、编译和执行 JavaScript 代码。尽管许多开发人员认为 JavaScript 是一种纯粹的解释型语言,但它的执行模型实际上更为复杂,涉及解析、编译、优化和执行等多个阶段。理解这种模型对于编写高性能代码和有效调试至关重要。
有关 JavaScript 基础知识和语言特性的信息,请参阅 JavaScript 基础。有关 JavaScript 中通用编程概念的详细信息,请参阅 入门。
JavaScript 的执行模型介于传统的解释型语言和编译型语言之间。虽然 JavaScript 代码以源代码(而非二进制)的形式分发,但现代 JavaScript 引擎在执行前采用复杂的技术来解析和转换代码。
来源: get-started/ch1.md313-378 get-started/ch2.md18-38
JavaScript 的执行涉及几个不同的阶段
这个多步骤过程允许 JavaScript 引擎在执行开始前检测错误,并在运行时应用复杂的优化。
在执行任何 JavaScript 代码之前,必须先对其进行解析。解析器读取源代码文本并将其转换为称为抽象语法树 (AST) 的数据结构。
在解析过程中
JavaScript 是一种被解析的语言,而不是纯粹的解释型语言,这可以从“早期错误”的存在中得到证明——这些错误在任何代码执行开始之前就被检测并报告,例如函数中重复的参数名。
“JS 源代码在执行前会被解析。规范要求如此,因为它需要‘早期错误’——代码中静态确定的错误,例如重复的参数名——在代码开始执行之前就被报告。如果不解析代码,这些错误就无法识别。” - get-started/ch1.md348-349
解析完成后,JavaScript 引擎将 AST 转换为一种优化的形式,通常是某种形式的字节码或中间表示 (IR)。
编译过程包括
这一步是 JavaScript 被更准确地描述为编译型语言而非纯解释型语言的原因:“解析后的 JS 被转换为优化的(二进制)形式,然后执行该‘代码’” get-started/ch1.md352-353
编译成字节码后,JavaScript 代码由 JavaScript 虚拟机执行。现代 JavaScript 引擎结合使用解释和即时 (JIT) 编译来实现最佳性能。
来源: get-started/ch1.md356-359 get-started/ch1.md385-391
执行过程通常包括
这种混合方法允许 JavaScript 通过解释快速启动,同时通过 JIT 编译实现热代码路径的高性能。
JavaScript 将每个文件视为一个独立的程序单元,这对代码的处理和执行方式有影响。
JavaScript 程序生命周期的关键特征
现代 JavaScript 开发通常涉及一个构建过程,该过程在代码到达 JavaScript 引擎之前对其进行转换。
现代 JavaScript 构建管道通常包括
这个构建过程有助于弥合现代 JavaScript 功能与需要支持它们的环境之间的差距:“转译是一个人为的、社区发明的术语,用于描述使用工具将程序的源代码从一种形式转换为另一种形式(但仍是文本源代码)” get-started/ch1.md216-217
现代 JavaScript 引擎包含多个协同工作的组件,以高效地执行代码。
来源: get-started/ch1.md356-367 get-started/ch1.md385-391
JavaScript 引擎的关键组件包括
| 组件 | 描述 |
|---|---|
| 解析器 | 将源代码转换为 AST,检测语法错误 |
| 编译器 | 将 AST 转换为字节码或机器码 |
| 解释器 | 逐行执行字节码 |
| JIT 编译器 | 优化频繁执行的代码路径 |
| 分析器 | 监控执行以识别优化机会 |
| 垃圾收集器 | 通过移除未使用的对象来管理内存 |
V8 (Chrome/Node.js)、SpiderMonkey (Firefox) 和 JavaScriptCore (Safari) 等现代引擎都遵循类似的架构,同时实现不同的优化策略。
WebAssembly (WASM) 代表了浏览器环境中代码执行方式的演进,它补充了 JavaScript 的执行模型。
WASM 在几个关键方面与 JavaScript 不同
WASM 不会取代 JavaScript,而是对其进行补充,允许使用 C++ 或 Rust 等语言编写性能关键的代码,同时仍能在浏览器环境中与 JavaScript 进行互操作。
了解 JavaScript 的执行模型有助于编写高性能代码。
基于执行模型的性能考虑
许多优化策略都是顺应 JavaScript 引擎的执行方式,而不是与之对抗。
JavaScript 的执行模型在不同环境中略有差异。
| 环境 | 引擎示例 | 独特特征 |
|---|---|---|
| 浏览器 | V8 (Chrome), SpiderMonkey (Firefox), JavaScriptCore (Safari) | DOM 集成、安全沙箱 |
| Node.js | V8 | 文件系统访问、单线程事件循环与异步 I/O |
| Deno | V8 | 默认安全,内置 TypeScript 支持 |
| 移动应用 | JavaScriptCore (React Native), V8 (NativeScript) | 资源有限,可能存在 JIT 限制 |
来源: get-started/ch1.md86-105 get-started/ch1.md128-154
虽然核心执行模型相似,但每个环境都提供了不同的 API 和优化优先级。浏览器 JavaScript 引擎必须处理各种 Web 内容,而 Node.js 则针对服务器工作负载进行优化。
严格模式通过强制执行更严格的解析和错误处理来影响 JavaScript 代码的执行方式。
严格模式(在 ES5 中引入)通过以下方式影响执行模型:
严格模式通过在文件或函数顶部放置 "use strict"; 来启用,并且“不应被视为对您不能做什么的限制,而应被视为最佳方式的指导” get-started/ch1.md417-418
JavaScript 的执行模型将解释和编译的元素结合在一个复杂的管道中,该管道同时支持快速启动和优化的性能。执行过程包括解析、编译为字节码、解释以及热代码路径的即时优化。
理解这个模型可以帮助开发者更高效地编写代码,通过顺应 JavaScript 引擎的运行方式,而不是与之对抗。虽然 JavaScript 最初是一个简单的脚本语言,但现代的 JavaScript 引擎采用了先进的技术,使其性能堪比传统的编译型语言,同时又保留了动态类型和运行时求值的灵活性。