如果 Javascript 是单线程的,它如何实现异步?
JavaScript 是单线程语言。这意味着它只有一个调用堆栈和一个内存堆。正如预期的那样,它按顺序执行代码,并且必须完成一段代码才能继续执行下一段。它是同步的,但有时这可能会造成危害。例如,如果一个函数需要一段时间才能执行,或者需要等待某些事情,它会在此期间冻结所有进程。
窗口警报功能就是一个很好的例子。alert("Hello World")
除非你点击“确定”并关闭警报,否则你根本无法与网页进行任何交互。你被困住了。
那么我们如何使用 Javascript 获取异步代码呢?
这得感谢 JavaScript 引擎(V8、Spidermonkey、JavaScriptCore 等等),它提供了一些 Web API 来在后台处理这些任务。调用栈会识别 Web API 的函数,并将它们交给浏览器处理。浏览器完成这些任务后,它们就会返回,并作为回调函数被推送到调用栈。
打开控制台,输入以下命令,window
然后按回车键。您将看到 Web API 提供的大部分功能,包括 Ajax 调用、事件监听器、fetch API 和 setTimeout。JavaScript 使用 C++ 等低级编程语言在后台执行这些操作。
让我们看一个简单的例子,在你的控制台中运行此代码:
console.log("first")
setTimeout(() => {
console.log("second")
}, 1000)
console.log("third")
我们得到了什么?
first
third
undefined
second
感觉很奇怪,对吧?好吧,让我们逐行分解一下:
console.log("first")
首先在堆栈上,因此它会被打印出来。接下来,引擎注意到 setTimeout 函数,它不会被 JavaScript 处理,因此会将其推送到 WebAPI 进行异步处理。调用堆栈继续执行,不再关心传递给 Web API 的代码,而是console.log("three")
直接打印出来。
接下来,JavaScript 引擎的事件循环启动,就像一个小孩在旅途中问“我们到了吗?”一样。它开始触发,等待事件被推送进去。由于setTimeout
尚未完成,它undefined
默认返回 ,因为它还没有被赋值。一旦回调最终命中,我们就会console.log("second")
打印出来。
有一个非常好的网站,可以放慢这一切的速度并展示这一切的发生。
我建议你在这个沙盒里尝试一下,这样可以帮助你巩固理解。它帮助我了解了异步代码在 JavaScript 单线程环境下是如何工作的。
文章来源:https://dev.to/bbarbour/if-javascript-is-single-threaded-how-is-it-asynchronous-56gd