JavaScript 幕后工作原理:JS 引擎和运行时
你可能知道你的代码以某种方式在浏览器中编译和执行,以显示你所创建的漂亮的 Web 应用。但你是否知道有哪些组件为实现输出发挥了作用?
让我们深入了解一下幕后的 JavaScript,你无法确切看到的抽象部分。
为什么一个看似抽象的主题对你来说很重要?对 JavaScript 内部工作原理的理解使你能够超越表面水平,从更深的角度来探索语言。
它提供了关于该语言的背景信息,以及 JavaScript 引擎如何优化代码。这将给你一些重要的基础知识,这些知识塑造了你写代码的方式。它还能帮助你写出更高效、可扩展和可维护的代码。
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 代码的环境。
JavaScript 运行时
将 JavaScript 运行时视为包含运行 JavaScript 所需的所有组件的房子。这个房子包括 JavaScript 引擎、Web API 和回调队列。
Web APIs 是提供给引擎的功能,但不是 JavaScript 语言的一部分。引擎可以通过浏览器访问它们,并有助于访问数据或增强浏览器功能。示例是文档对象模型(DOM)和获取 API。
回调队列包括准备好执行的回调函数。回调队列确保回调以先进先出(FIFO)方法执行,并在堆栈为空时将其传递到堆栈中。
浏览器运行时和 Node.js 是运行时环境的例子。
当 JavaScript 在 Web 浏览器中执行时,它是在浏览器的运行时环境中运行的。浏览器运行时环境提供对 DOM 的访问,从而实现了与网页元素的交互,处理事件,以及对页面结构的操作。
Node.js 提供了一个服务器端运行时环境,用于在浏览器外部执行 JavaScript。因为它在浏览器外部执行 JavaScript,所以它不能访问 Web API。Node.js 运行时环境将其替换为叫作 C++ 绑定和线程池的东西。
JavaScript 的优化策略
现代 JavaScript 引擎有一些优化策略,以提高代码执行的性能。这些优化在执行过程中是动态发生的。让我们来看看其中一些策略。
即时编译
涉及到将 JavaScript 代码转换成机器代码的过程是使用编译和解释进行的。
在编译中,整个源代码被一次性转换成机器代码,并写入二进制文件,由计算机执行。
相反,在解释期间,解释器逐行解释源代码,遇到一行就执行它。
JavaScript 曾经是一种解释型语言,但解释型语言与编译型语言相比速度较慢。
为了优化 Web 应用程序的性能,JavaScript 结合了编译和解释。这叫作即时编译。该方法一次性将全部代码编译成机器码并执行。
即时编译涉及与常规编译相同的两个过程,但这里的机器代码没有写入二进制文件。代码也是在编译后立即执行的。
这对 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 功能的未来变化。
为了更深入地学习,你可以访问这些资源:
执行环境 事件循环 微任务队列
Happy coding!
原文链接:https://www.freecodecamp.org/news/how-javascript-works-behind-the-scenes/
作者:Esther Christopher
译者:Chengjun.L