JavaScript事件循环按“宏任务→清空微任务→下一个宏任务”调度;setTimeout(fn,0)属宏任务,总在微任务后执行;Promise.then、async/await属微任务;queueMicrotask更轻量;事件循环还协调渲染、输入等跨系统任务。

JavaScript 事件循环不是“等一个任务做完再做下一个”,而是严格按“宏任务 → 清空微任务 → 下一个宏任务”节奏推进的调度机制。
为什么 setTimeout(fn, 0) 不是立刻执行?
因为 setTimeout 的回调被归类为宏任务,必须等当前同步代码 + 所有已排队微任务执行完后,才轮到它。即使延迟设为 0,它也永远排在 Promise 回调之后。
-
浏览器会强制把
setTimeout的回调塞进宏任务队列,哪怕延迟为 0 - 微任务(如
Promise.then、queueMicrotask)总是在当前宏任务结束后、下一个宏任务开始前“插队”执行 - 真实延迟还受浏览器最小计时精度限制(通常 ≥ 4ms),
setTimeout(fn, 0)实际可能延后 4–10ms
Promise.then 和 async/await 都走微任务队列
async/await 是 Promise 的语法糖,await 后面的代码会被编译成 Promise.then 形式,因此也进入微任务队列。
-
await Promise.resolve()后的语句,和写Promise.resolve().then(...)效果一致,都是微任务 - 连续多个
await不会“打断”微任务链;但若中间夹了setTimeout或用户交互事件,就会切出宏任务上下文 - 注意:
await一个未 resolve 的 Promise 会挂起当前 async 函数,但它本身不产生微任务,只等 Promise 状态变更后触发后续微任务
调试时怎么看清任务执行顺序?
靠 console.log 容易误判,因为输出时机受渲染、缓冲影响;更可靠的是用 debugger 断点 + 浏览器 DevTools 的 “Tasks” 和 “Microtasks” 面板(Chrome > Rendering > Event Loop Visualization)。
立即学习“Java免费学习笔记(深入)”;
- 在关键位置加
debugger,观察调用栈清空后,DevTools 是否立即跳入Promise.then回调 - 避免在微任务里做大量计算——它会阻塞下一个宏任务(比如页面渲染、用户点击响应)
- 想手动插入微任务?用
queueMicrotask(() => {...}),比Promise.resolve().then(...)更轻量、语义更清晰
最常被忽略的一点:事件循环不是只管 JS 代码,它还协调渲染、用户输入、网络响应等跨系统任务。比如一个耗时 100ms 的同步函数,不仅卡住 JS 执行,还会导致页面掉帧、input 延迟响应——这时候问题不在“事件循环没跑”,而在于你让事件循环的“单线程”干了本该拆分或移交 Web Worker 的活。










