0Pricing
Frontend Academy · 课时

事件循环:调用栈、队列与微任务

直观了解 JavaScript 的执行过程:调用栈、任务队列、由 Promise 产生的微任务,以及异步操作期间界面为何不会阻塞。

事件循环:调用栈、队列与微任务 是 CoddyKit 上的免费 Frontend Academy 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Frontend Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Frontend Academy 课程共包含 4 节课。

JavaScript 是单线程的

JavaScript 在单个线程上运行,一次只能执行一段代码。然而,它可以在不阻塞的情况下处理数千个异步操作(网络请求、计时器和界面事件)。这是如何做到的?答案是事件循环。

调用栈

调用栈用于记录 JavaScript 当前正在运行的函数。调用函数时,会将一个栈帧压入栈中;函数返回时,会将该栈帧弹出。如果栈已满(例如发生无限递归),就会出现栈溢出。

function c() { /* does something */ }
function b() { c(); }
function a() { b(); }
a();

// Call stack during c():
// c    ← top
// b
// a
// (anonymous)

Web API——卸载工作

当 JavaScript 调用异步函数(setTimeout、fetch、addEventListener)时,浏览器会将工作交给Web API。JS 引擎会继续执行下一行代码,而不会等待。

console.log('1');
setTimeout(() => console.log('2'), 0); // handed to Web API
console.log('3');
// Output: 1, 3, 2

任务队列(宏任务队列)

当 Web API 操作完成时(计时器触发、fetch 完成),回调会被放入任务队列(宏任务队列)。只有当调用栈为空时,事件循环才会将回调移入调用栈。

微任务队列

Promise 使用微任务队列——一个高优先级队列。每个任务完成后、拾取下一个任务之前,所有待处理的微任务都会被执行完毕。微任务总是在下一个宏任务之前运行。

console.log('1');
setTimeout(() => console.log('timeout'), 0);  // macrotask
Promise.resolve().then(() => console.log('promise')); // microtask
console.log('2');
// Output: 1, 2, promise, timeout

事件循环的运行过程

事件循环的算法如下:1)执行调用栈中的当前任务,直到调用栈为空。2)执行完微任务队列中的全部任务。3)视情况执行渲染。4)取出下一个宏任务并重复上述过程。

queueMicrotask()

queueMicrotask(fn) 会将回调安排到微任务队列中,而不会创建 Promise。通常不需要直接使用它,但了解它有助于理解库如何实现任务调度。

queueMicrotask(() => console.log('microtask'));
console.log('sync');
// Output: sync, microtask

长任务为何会冻结界面

如果同步任务运行时间过长(例如紧密循环处理大量数据),事件循环就无法处理界面事件或执行渲染,页面也会因此冻结。请使用 setTimeout 将长时间工作拆分成多个小块,把控制权交还给事件循环。

// Bad — blocks the UI for seconds:
for (let i = 0; i < 10_000_000; i++) { /* heavy work */ }

// Better — yield periodically:
async function processInChunks(items) {
  for (let i = 0; i < items.length; i++) {
    process(items[i]);
    if (i % 100 === 0) await new Promise(r => setTimeout(r, 0)); // yield
  }
}

Web Worker——真正的并行处理

对于 CPU 密集型工作,请使用 Web Worker——一个拥有独立事件循环的单独线程。Worker 通过 postMessage 与主线程通信。没有共享内存,也就没有竞争条件。

requestAnimationFrame

requestAnimationFrame(callback) 会在浏览器下一次重绘前安排一个回调。请将它用于由 JavaScript 驱动的动画——它会与显示器的刷新率同步(通常为每秒 60 帧)。

function animate(timestamp) {
  // update positions based on timestamp
  element.style.transform = `translateX(${timestamp * 0.1 % 400}px)`;
  requestAnimationFrame(animate);
}
requestAnimationFrame(animate);

事件循环可视化

latentflip.com/loupe 上的交互式演示可以让您粘贴代码,并以慢动作观察调用栈、Web API、任务队列和事件循环的运行过程。它对于建立直觉理解非常重要。

快速检查

Promise 完成后,其 .then() 回调会在哪个队列中运行?

回顾:事件循环

JavaScript 是单线程的。异步工作会交给 Web API。回调会通过任务队列(setTimeout、fetch 回调)或微任务队列(Promise)返回。事件循环会在每个任务之后、下一次渲染或宏任务之前执行完所有微任务。长时间运行的同步代码会阻塞一切操作。

常见问题解答

「事件循环:调用栈、队列与微任务」课时是免费的吗?

是的 — 「事件循环:调用栈、队列与微任务」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Frontend Academy 课程的其余内容,请升级到 CoddyKit PRO。 Frontend Academy 课程共包含 4 节课。

「事件循环:调用栈、队列与微任务」这节课中我会学到什么?

直观了解 JavaScript 的执行过程:调用栈、任务队列、由 Promise 产生的微任务,以及异步操作期间界面为何不会阻塞。 你通过在浏览器中直接运行的动手代码来练习 Frontend Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Frontend Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Frontend Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。

「事件循环:调用栈、队列与微任务」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Frontend Academy 课中编写并运行代码吗?

能。每节 Frontend Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 事件循环:调用栈、队列与微任务
  2. 回调与回调地狱
  3. Promise:then、catch、finally 与 Promise.all
  4. async/await 与错误处理
← 返回 Frontend Academy