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