✨♻️ JavaScript 可视化:事件循环

2025-05-25

✨♻️ JavaScript 可视化:事件循环

如果您在 2024 年(或更晚)来到这里,这里有一篇更新的博客文章!](https://lydiahallie.com/blog/event-loop


哦,天哪,事件循环!这是每个 JavaScript 开发者都要处理的事情之一,但一开始理解起来可能有点令人困惑。我是一个视觉学习者,所以我想尝试用低分辨率的 gif 来直观地解释一下,因为现在已经 2019 年了,gif 仍然像素化,而且很模糊。

但首先,什么是事件循环以及为什么要关心它?

JavaScript 是单线程的:一次只能运行一个任务。通常这没什么大不了的,但现在想象一下你正在运行一个需要 30 秒的任务……是的……在执行该任务期间,我们需要等待 30 秒才能执行其他操作(JavaScript 默认在浏览器的主线程上运行,因此整个 UI 都卡住了)😬 现在是 2019 年了,没人想要一个缓慢、无响应的网站。

幸运的是,浏览器为我们提供了 JavaScript 引擎本身不提供的一些功能:Web API。这包括 DOM API、setTimeoutHTTP 请求等等。这可以帮助我们创建一些异步、非阻塞的行为 🚀

当我们调用一个函数时,它会被添加到一个叫做调用栈的地方。调用栈是 JS 引擎的一部分,它与浏览器无关。它是一个栈,这意味着它是先进后出的(想象一下一堆煎饼)。当一个函数返回一个值时,它会被弹出栈👋

respond函数返回一个setTimeout函数。这setTimeout是 Web API 提供给我们的东西:它让我们可以延迟执行任务而不会阻塞主线程。我们传递给该setTimeout函数的回调函数(箭头函数() => { return 'Hey')会被添加到 Web API 中。与此同时,该setTimeout函数和 respond 函数都会从堆栈中弹出,它们都会返回各自的值!

在 Web API 中,计时器的运行时间与我们传递给它的第二个参数 1000 毫秒相同。回调不会立即添加到调用堆栈,而是被传递到称为队列的地方。

这可能会让人感到困惑:这并不意味着回调函数会在 1000 毫秒后被添加到调用栈(从而返回一个值)!它只是在 1000 毫秒后被添加到队列中。但既然是队列,函数就必须等待轮到它!

现在到了我们翘首以盼的部分……事件循环该执行它唯一的任务了:将队列与调用栈连接起来!如果调用栈为,也就是说所有之前调用的函数都已返回值并被弹出栈,那么队列中的第一个函数就会被添加到调用栈中。在本例中,没有其他函数被调用,这意味着当回调函数成为队列中的第一个函数时,调用栈已经为空。

回调被添加到调用堆栈,被调用,返回一个值,然后从堆栈中弹出。


阅读文章很有趣,但只有反复练习才能真正适应。试想一下,如果我们运行以下命令,控制台会输出什么:




const foo = () => console.log("First");
const bar = () => setTimeout(() => console.log("Second"), 500);
const baz = () => console.log("Third");

bar();
foo();
baz();


Enter fullscreen mode Exit fullscreen mode

明白了吗?让我们快速看一下在浏览器中运行此代码时会发生什么:

  1. 我们调用barbar返回一个setTimeout函数。
  2. 我们传递的回调setTimeout被添加到 Web API,该setTimeout函数从bar调用堆栈中弹出。
  3. 计时器运行,同时foo被调用并记录Firstfoo返回(未定义),baz被调用,并且回调被添加到队列中。
  4. baz日志Third。事件循环在baz返回后看到调用堆栈为空,之后回调被添加到调用堆栈中。
  5. 回调日志Second

希望以上内容能让您对事件循环更加熟悉!如果您仍然感到困惑,也不用担心,最重要的是了解某些错误/行为的来源,以便高效地在 Google 上搜索到正确的术语,最终找到正确的 Stack Overflow 页面。💪🏼如有任何疑问,请随时联系我!

文章来源:https://dev.to/lydiahallie/javascript-visualized-event-loop-3dif
PREV
💡🎁 JavaScript 可视化:生成器和迭代器
NEXT
⭐️ 交互式 JavaScript 测验 #1