JavaScript 核心约 6 分钟

把事件循环讲成一句话:先同步,再微任务

面试里常被问到的事件循环,本质上是一轮一轮的清空顺序。这篇笔记用最小的例子说明同步代码、微任务与渲染之间的关系。

纸面手绘的 JavaScript 事件循环与任务队列时间线示意
本文目录

浏览器里的 JavaScript 是单线程的,但页面并不会因为一个网络请求卡住。原因在于调用栈只执行同步代码,异步任务完成后会把回调放进队列,等调用栈空了再取出执行。

一轮事件循环做了什么

每一轮大致按这个顺序:先执行当前调用栈里的同步代码,调用栈清空后取出所有微任务逐个执行,微任务队列清空后才可能发生渲染,然后进入下一个宏任务。

JavaScript
console.log("1 同步");

setTimeout(() => console.log("4 宏任务"), 0);

Promise.resolve().then(() => console.log("3 微任务"));

console.log("2 同步");

// 输出顺序:1 同步 → 2 同步 → 3 微任务 → 4 宏任务

微任务先于下一次渲染

如果把耗时逻辑放进微任务,它会排在渲染之前执行,页面的样式更新会被推迟。这也是为什么大循环里连续使用 Promise 会让人感觉页面卡顿,而不是分批更新。

  • 微任务:Promise 回调、queueMicrotask、MutationObserver
  • 宏任务:setTimeout、事件回调、网络请求完成回调
  • 需要让页面先刷新:把逻辑放进 setTimeout 或 requestAnimationFrame

await 之后的代码为什么像微任务

async 函数里的 await 会把后面的代码挂起,等 Promise 落定后再恢复执行,而恢复的过程正是排进微任务队列。所以 await 之后的语句虽然写在函数里,执行时机依然晚于当前的同步代码。

记住清空顺序就够了:同步执行完,微任务清空,再渲染,再取下一个宏任务。

相关阅读