为什么 JavaScript 是一种可以非阻塞的单线程语言?
chrome 浏览器中的 JavaScript 由 V8 引擎实现。
- V8 发动机有两个部分:
- 内存堆
- 调用堆栈
内存堆:用于分配 JavaScript 程序使用的内存。请记住,内存堆与堆数据结构不同,它们完全不同。它是操作系统内部的可用空间。
调用堆栈:在调用堆栈中,您的 JS 代码被读取并逐行执行。
现在,JavaScript 是一种单线程语言,这意味着它只有一个用于执行程序的调用堆栈。调用堆栈与您可能在数据结构中读到的堆栈数据结构相同。正如我们所知,堆栈是先进后出的 FILO。同样,在调用堆栈中,只要一行代码进入调用堆栈,它就会被执行并移出堆栈。这样一来,由于只有一个调用栈,JavaScript 是一种单线程语言。
JavaScript 是一种单线程语言,因为在单线程上运行代码时,它非常容易实现,因为我们不必处理多线程环境中出现的复杂场景,如死锁。
由于 JavaScript 是一种单线程语言,因此它本质上是同步的。现在,您会想知道您在 JavaScript 中使用了异步调用,那么这可能吗?
那么,让我向您解释一下 JavaScript 中异步调用的概念,以及单线程语言如何实现它。在解释它之前,让我们简要讨论一下为什么我们需要异步调用或异步调用。正如我们在同步调用中所知道的那样,所有工作都是逐行完成的,即执行第一个任务然后执行第二个任务,无论一个任务需要多长时间。这就产生了时间浪费和资源浪费的问题。异步调用克服了这两个问题,其中一个不等待一个调用完成,而是同时运行另一个任务。因此,当我们必须进行图像处理或通过网络发出 API 调用等请求时,我们会使用异步调用。
现在,回到上一个关于如何在 JS 中使用异步调用的问题。在 JS 中,我们有一个词法环境、语法解析器、一个用于执行 JS 代码的执行上下文(内存堆和调用堆栈)。但除了这些浏览器之外,还有事件循环、回调队列和也用于运行 JS 代码的 WebAPI。虽然这些不是 JS 的一部分,但它也有助于正确执行 JS,因为我们有时会在 JS 中使用浏览器功能。
如上图所示,DOM、AJAX 和 Timeout 实际上并不是 JavaScript 的一部分,而是 RunTime Environment 或浏览器的一部分,因此这些可以使用回调队列在 WebAPI 中异步运行,然后再次放入调用堆栈使用事件循环执行。
让我们举个例子来非常清楚这个概念。假设我们想要在 JS 运行时环境中执行以下代码。
例子:
输出:
A C B
让我们看看为什么会发生这种情况,因为 JavaScript 是一种单线程语言,所以输出应该是ABC ,但事实并非如此。
当 JS 尝试执行上述程序时,它会将第一条语句放入调用堆栈中,该语句将被执行并在控制台中打印 A 并从堆栈中弹出。现在,它将第二条语句放入调用堆栈中,当它尝试执行该语句时,它发现 setTimeout() 不属于 JS,因此它弹出该函数并放入 WebAPI 以在那里执行。由于调用堆栈现在又是空的,它将第三条语句放入堆栈并执行它,从而在控制台中打印 C。
同时,WebAPI 执行超时函数并将代码放入回调队列中。 eventloop 检查调用堆栈是否为空,或者回调队列中是否有任何语句需要一直执行。一旦事件循环检查调用堆栈为空并且回调队列中有需要执行的内容,它将语句放入调用堆栈,调用堆栈执行语句并在控制台中打印 B浏览器。