原文: How Does JavaScript Work Behind the Scenes? JS Engine and Runtime Explained

你可能知道你的代码以某种方式在浏览器中编译和执行,以显示你所创建的漂亮的 Web 应用。但你是否知道有哪些组件为实现输出发挥了作用?

让我们深入了解一下幕后的 JavaScript,你无法确切看到的抽象部分。

为什么一个看似抽象的主题对你来说很重要?对 JavaScript 内部工作原理的理解使你能够超越表面水平,从更深的角度来探索语言。

它提供了关于该语言的背景信息,以及 JavaScript 引擎如何优化代码。这将给你一些重要的基础知识,这些知识塑造了你写代码的方式。它还能帮助你写出更高效、可扩展和可维护的代码。

JavaScript 引擎

09BA18A6-3F7A-4DBE-AA43-C482725CA5E4
JavaScript 引擎显示调用栈和堆

简单地说,JavaScript 引擎是一个解释 JavaScript 代码的计算机程序。该引擎负责执行代码。

每个主流的浏览器都有一个可以执行 JavaScript 代码的 JavaScript 引擎。最流行的是谷歌浏览器 Chrome 的 V8 引擎。谷歌的 V8 为 Chrome 和 Node.js 提供支持,Node.js 是一个后端 JavaScript 运行环境,用于构建服务器端应用程序。

其他主要的浏览器引擎包括:

  • 由 Mozilla 为 Firefox 开发的 SpiderMonkey
  • 为 Safari 浏览器提供支持的 JavaScriptCore
  • 为 Internet Explorer 提供支持的 Chakra

任何 JavaScript 引擎通常都包含一个调用栈和一个堆。调用栈是代码执行的地方。堆是一个非结构化的内存池,用于存储应用程序所需的所有对象。

由于计算机的处理器只能理解二进制、0 和 1,因此必须将代码转换为 0 和 1。

当代码片段进入引擎时,代码首先被解析,也就是被读取。该代码随后被解析为叫作抽象语法树(AST)的数据结构。生成的树被用来来创建机器代码。

执行发生在使用执行上下文的JavaScript引擎调用栈中。这是执行 JavaScript 代码的环境。

FA4EDBD9-0348-4445-B795-8D1FEF904CBE
显示 JavaScript 执行过程的图表说明

JavaScript 运行时

将 JavaScript 运行时视为包含运行 JavaScript 所需的所有组件的房子。这个房子包括 JavaScript 引擎、Web API 和回调队列。

Web APIs 是提供给引擎的功能,但不是 JavaScript 语言的一部分。引擎可以通过浏览器访问它们,并有助于访问数据或增强浏览器功能。示例是文档对象模型(DOM)和获取 API。

CDFBBA53-5533-478E-91CE-5904714E1043
浏览器中的 JavaScript 运行时图,包含 JavaScript 引擎、WEB API 和回调队列

回调队列包括准备好执行的回调函数。回调队列确保回调以先进先出(FIFO)方法执行,并在堆栈为空时将其传递到堆栈中。

浏览器运行时和 Node.js 是运行时环境的例子。

当 JavaScript 在 Web 浏览器中执行时,它是在浏览器的运行时环境中运行的。浏览器运行时环境提供对 DOM 的访问,从而实现了与网页元素的交互,处理事件,以及对页面结构的操作。

Node.js 提供了一个服务器端运行时环境,用于在浏览器外部执行 JavaScript。因为它在浏览器外部执行 JavaScript,所以它不能访问 Web API。Node.js 运行时环境将其替换为叫作 C++ 绑定和线程池的东西。

JavaScript 的优化策略

现代 JavaScript 引擎有一些优化策略,以提高代码执行的性能。这些优化在执行过程中是动态发生的。让我们来看看其中一些策略。

即时编译

涉及到将 JavaScript 代码转换成机器代码的过程是使用编译和解释进行的。

在编译中,整个源代码被一次性转换成机器代码,并写入二进制文件,由计算机执行。

EB039874-52DC-4CD8-B95C-F9E75F2D2283_4_5005_c
显示代码编译过程的图表

相反,在解释期间,解释器逐行解释源代码,遇到一行就执行它。

D3DC97A6-3D79-46E0-A2F2-BB3FA694F0EF_4_5005_c
显示代码解释过程的图表

JavaScript 曾经是一种解释型语言,但解释型语言与编译型语言相比速度较慢。

为了优化 Web 应用程序的性能,JavaScript 结合了编译和解释。这叫作即时编译。该方法一次性将全部代码编译成机器码并执行。

E2BA4399-5F52-408C-B2AB-A9E6F74B3238_4_5005_c
显示代码的即时编译的图表

即时编译涉及与常规编译相同的两个过程,但这里的机器代码没有写入二进制文件。代码也是在编译后立即执行的。

这对 JavaScript 中的代码执行速度产生了重大影响。所以希望这有助于消除 JavaScript 是一种纯解释型语言的观念。

为了完全优化 JavaScript 代码,引擎首先创建一个未优化版本的机器代码,以便它可以立即开始执行。同时,代码被重新优化并在当前运行的程序执行的后台重新编译。这是多次进行的,以产生最终的、最优化的版本。

解析、编译和执行的过程发生在引擎中无法从代码访问的一些特殊线程中。

什么是内联?

内联是 JavaScript 用来提高性能和速度的另一种优化技术。

function add(a, b) {
  return a + b;
}

let result = 0;
result = result + 5;
result = result + 3;

console.log(result); //

在这个片段中,原 add() 函数没有被直接调用。相反,函数 return a + b; 里面的代码被插入到调用位置。

这种优化特别针对那些被反复调用的函数。 JavaScript 引擎会像正常情况下那样运行该函数。但由于该函数经常被调用,引擎会在调用位置用函数的实际代码替换函数调用。这有助于防止多次函数调用,提高性能。

性能方面的考虑

有几个因素会影响你的 Web 应用程序的性能。由于 JavaScript 引擎采用了一些策略来确保优化,也有一些最佳实践需要开发人员考虑,以实现高效的执行。

诸如尽量减少 DOM 操作和减少函数调用等技术可以提高代码性能。

对 DOM 的频繁访问和互动会减慢网页的渲染速度,导致性能滞后。既然你不能完全避免与 DOM 的交互,你可以通过批量 DOM 更新来减少交互,以降低开销。

此外,减少函数调用可以使性能提高一个档次。通过减少函数调用,你可以精简你的代码,使其更有效率,使你的 JavaScript 应用程序更快、响应更灵敏。

// 带有不必要的函数调用的低效代码
function calculateTotal(a, b, c) {
  return addNumbers(a, b) + multiplyNumbers(c, b);
}
function addNumbers(x, y) {
  return x + y;
}
function multiplyNumbers(x, y) {
  return x * y;
}
// 改进代码,减少函数调用
function calculateTotal(a, b, c) {
  const sum = a + b;
  return sum + c * b;
}
console.log(calculateTotal(2, 3, 4)); // Output: 23

在低效代码中,calculateTotal() 函数对 addNumbers()multiplyNumbers() 进行单独的函数调用。这导致了函数调用的开销。

在改进后的代码中,通过在 calculateTotal() 函数中直接执行加法和乘法操作,减少了函数调用。通过减少函数调用,代码变得更加有效,并提高了执行速度。

未来 JavaScript 的发展和趋势

JavaScript 引擎和运行时环境将继续得到改进和提高。这些变化都是为了提高 Web 应用的性能。

其中一个进步就是 WebAssembly 的崛起。WebAssembly 给 Web 应用带来了接近原生的性能,并支持多种语言。它为性能优化和执行速度带来了新的可能性。

对 JavaScript 开发人员来说,重要的是要跟上这些趋势,并相应地调整新的编码最佳实践。

总结

你的 JavaScript 代码是如何被解析的,直到它呈现出一个功能性的 Web 应用,这其中涉及到很多过程。

这篇文章对主要概念进行了高层次的概述。它解释了JavaScript引擎如何执行代码、运行时和它的组件。它还继续解释了优化策略并强调了性能方面的考虑。

了解 JavaScript 是如何在幕后运行的,可以塑造开发人员处理问题和编写更有效代码的方式。它还可以帮助开发者在学习曲线上保持领先,并轻松适应 JavaScript 功能的未来变化。

为了更深入地学习,你可以访问这些资源:

如果你喜欢阅读这篇文章,那么请在 TwitterLinkedIn 上与我联系,我在那里分享知识。