JavaScript事件循环本质是单线程+任务队列+调用栈协作模型;setTimeout(0)不立即执行因回调入宏任务队列,须等同步代码和所有微任务(如Promise.then)执行完才执行。

JavaScript 的事件循环不是“多线程并发”,它本质是单线程 + 任务队列 + 调用栈协作的模型;理解错这点,所有 setTimeout、Promise.then、await 行为都会误判。
为什么 setTimeout(0) 不会立刻执行
因为 setTimeout 的回调被放入「宏任务队列」,必须等当前调用栈清空、且所有微任务(如 Promise.then)执行完后,才轮到它。即使设为 0,也得排队。
- 当前同步代码(比如 console.log)优先执行
- 然后立即执行所有已入队的微任务(
Promise.resolve().then()) - 最后才从宏任务队列里取一个(比如那个
setTimeout回调)
console.log(1); setTimeout(() => console.log(2), 0); Promise.resolve().then(() => console.log(3)); console.log(4); // 输出:1 → 4 → 3 → 2
微任务 vs 宏任务:哪些属于哪一类
区分关键不在“快慢”,而在**执行时机优先级**:微任务总在每次宏任务结束后、渲染前集中清空;宏任务则按插入顺序,一次只取一个。
- 微任务:
Promise.then/catch/finally、queueMicrotask()、MutationObserver - 宏任务:
setTimeout、setInterval、setImmediate(Node.js)、I/O 回调、UI 渲染触发的事件(如click)
注意:async/await 内部 await 后的代码,会被编译成 Promise.then,所以属于微任务。
立即学习“Java免费学习笔记(深入)”;
Node.js 和浏览器的事件循环有差异
浏览器没有明确的「check 阶段」或「close callback 阶段」,而 Node.js 的 libuv 事件循环分 6 个阶段,其中 process.nextTick() 甚至比微任务还早——它在每个阶段切换前立即执行,不属于 Promise 规范,但优先级最高。
-
process.nextTick()> 微任务 >setTimeout(即使为 0) - Node.js 中
setImmediate()属于 check 阶段,在 I/O 回调之后、timers 之前;浏览器不支持该 API - 跨平台不要依赖
process.nextTick或setImmediate的精确时序
实际调试中容易忽略的陷阱
很多人以为「事件循环 = 所有异步都靠它调度」,其实 DOM 渲染、网络请求、定时器底层都由浏览器/运行时单独线程处理,JS 主线程只负责接收回调并排队——也就是说,fetch() 本身不进事件循环,它的 .then() 才进。
- 长时间同步代码(如 while(true))会完全阻塞事件循环,导致页面卡死、定时器延迟、Promise 不执行
-
requestIdleCallback不是微任务,它在浏览器空闲时段执行,也不保证一定执行 - 使用
Web Worker是唯一真正脱离主线程的方式,但 Worker 里没有 DOM、没有setTimeout(只有self.setTimeout),事件循环逻辑独立
真正难的不是记住阶段顺序,而是意识到:所谓「异步」,只是把回调交出去排队;而「并发」在 JS 里从来不存在——只有协作式让出控制权。










