JavaScript 的工作原理:引擎、运行时和调用堆栈概述

2021-07-25 00:54:43

随着 JavaScript 变得越来越流行,团队正在其堆栈的多个层面上利用它的支持 - 前端、后端、混合应用程序、嵌入式设备等等。这篇文章是旨在深入研究 JavaScript 及其实际工作原理的系列文章中的第一篇:我们认为通过了解 JavaScript 的构建块以及它们如何协同工作,您将能够编写更好的代码并应用。我们还将分享我们在构建 SessionStack 时使用的一些经验法则,SessionStack 是一个轻量级的 JavaScript 应用程序,必须健壮和高性能才能保持竞争力。如 GitHub 统计数据所示,JavaScript 在 GitHub 中的活动存储库和总推送方面名列前茅。它在其他类别中也不落后太多。如果项目越来越依赖 JavaScript,这意味着开发人员必须利用语言和生态系统提供的一切,对内部结构有越来越深入的了解,以便构建出色的软件。事实证明,有很多开发人员每天都在使用 JavaScript,但并不了解幕后发生的事情。几乎每个人都听说过 V8 引擎这个概念,大多数人都知道 JavaScript 是单线程的,或者它使用的是回调队列。在这篇文章中,我们将详细介绍所有这些概念,并解释 JavaScript 的实际运行方式。通过了解这些细节,您将能够编写更好的、无阻塞的应用程序,并正确利用所提供的 API。

如果您对 JavaScript 比较陌生,这篇博文将帮助您理解为什么 JavaScript 与其他语言相比如此“奇怪”。如果您是一位经验丰富的 JavaScript 开发人员,希望它能给您一些关于您每天使用的 JavaScript 运行时实际工作方式的全新见解。 JavaScript 引擎的一个流行示例是 Google 的 V8 引擎。例如,V8 引擎用于 Chrome 和 Node.js。下面是它的一个非常简化的视图:引擎由两个主要组件组成: * 内存堆——这是内存分配发生的地方 * 调用栈——这是你的代码执行时的堆栈帧所在的地方有 API几乎所有 JavaScript 开发人员都使用过的浏览器(例如“setTimeout”)。但是,这些 API 不是由引擎提供的。所以,我们有引擎,但实际上还有更多。我们有浏览器提供的称为 Web API 的东西,比如 DOM、AJAX、setTimeout 等等。 JavaScript 是一种单线程编程语言,这意味着它只有一个调用堆栈。因此它一次只能做一件事。

调用栈是一种数据结构,它基本上记录了我们在程序中的位置。如果我们进入一个函数,我们会将它放在栈顶。如果我们从一个函数返回,我们就会从栈顶弹出。这就是堆栈可以做的所有事情。函数乘法(x,y){返回x * y; } function printSquare(x) { var s = multiply(x, x);控制台.log(s); } printSquare(5);当引擎开始执行此代码时,调用堆栈将为空。之后,步骤如下: 这就是在抛出异常时构建堆栈跟踪的方式——它基本上是异常发生时调用堆栈的状态。看看下面的代码: function foo() { throw new Error('SessionStack will help you resolve crashes :)'); } 功能栏() { foo(); } 函数开始() { bar(); } 开始();如果在 Chrome 中执行此操作(假设此代码位于名为 foo.js 的文件中),将生成以下堆栈跟踪:“Blowing the stack”——当您达到最大调用堆栈大小时会发生这种情况。这很容易发生,特别是如果您使用递归而没有非常广泛地测试您的代码。看看这个示例代码:

当引擎开始执行这段代码时,它首先调用函数“foo”。然而,这个函数是递归的,并且在没有任何终止条件的情况下开始调用自身。因此,在执行的每一步,相同的函数都会一遍又一遍地添加到调用堆栈中。它看起来像这样:然而,在某些时候,调用堆栈中的函数调用数量超过了调用堆栈的实际大小,浏览器决定采取行动,通过抛出一个错误,它可能看起来像这样:在单线程上运行代码非常容易,因为您不必处理多线程环境中出现的复杂场景 — 例如,死锁。但是在单个线程上运行也非常有限。由于 JavaScript 只有一个调用堆栈,所以当事情变慢时会发生什么?如果调用堆栈中的函数调用需要花费大量时间来处理,会发生什么情况?例如,假设您想在浏览器中使用 JavaScript 进行一些复杂的图像转换。你可能会问——为什么这甚至是一个问题?问题是,虽然调用堆栈有要执行的函数,但浏览器实际上不能做任何其他事情——它被阻塞了。这意味着浏览器无法渲染,无法运行任何其他代码,只是卡住了。如果你想在你的应用中使用流畅的 UI,这就会产生问题。这不是唯一的问题。一旦您的浏览器开始在调用堆栈中处理如此多的任务,它可能会在很长一段时间内停止响应。大多数浏览器通过引发错误来采取行动,询问您是否要终止网页。

那么,我们如何才能在不阻塞 UI 并使浏览器无响应的情况下执行繁重的代码呢?嗯,解决方案是异步回调。这将在“JavaScript 的实际工作原理”教程的第 2 部分:“深入了解 V8 引擎 + 关于如何编写优化代码的 5 个技巧”中进行更详细的解释。同时,如果您很难重现和理解 JavaScript 应用程序中的问题,请查看 SessionStack。 SessionStack 记录您的 Web 应用程序中的所有内容:所有 DOM 更改、用户交互、JavaScript 异常、堆栈跟踪、失败的网络请求和调试消息。使用 SessionStack,您可以将 Web 应用程序中的问题作为视频回放,并查看发生在用户身上的所有事情。