javascript事件循环是单线程下异步调度的核心机制,由执行栈、宏任务队列和微任务队列协同构成;每轮循环依次执行一个宏任务、清空微任务队列、可选渲染,再取下一个宏任务。

JavaScript 的事件循环不是“后台自动运行的黑箱”,而是由明确角色分工、严格执行顺序构成的一套调度规则。它让单线程的 JS 能流畅响应用户操作、处理网络请求、执行定时任务,关键在于分清“谁在什么时候做什么”。
执行栈 + 两个队列:事件循环的三大支柱
事件循环依赖三个核心组件协同工作:
- 执行栈(Call Stack):同步代码的执行场所。函数调用入栈,执行完毕出栈;栈空是触发下一轮调度的前提。
- 宏任务队列(Macrotask Queue):存放 setTimeout、setInterval、I/O 回调、script 整体代码等耗时较长的任务。每次只取一个执行。
- 微任务队列(Microtask Queue):存放 Promise.then/catch/finally、queueMicrotask、MutationObserver 回调等轻量级任务。只要宏任务执行完,立刻清空整个微任务队列,不中断。
一次完整循环:四步不可跳过的节奏
浏览器环境下的标准流程如下(以一次 tick 为例):
- 执行当前宏任务(如一个 setTimeout 回调或初始脚本)
- 立即执行所有已排队的微任务(Promise 回调全部跑完,中途不插入新宏任务)
- 进行页面渲染(如有 DOM 变更且时机合适,浏览器可能在此刻更新 UI)
- 从宏任务队列中取出下一个任务,开始新一轮循环
宏任务与微任务的优先级差异直接影响代码行为
常见误区是认为“先注册先执行”,实际取决于任务类型和插入时机。例如:
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
输出一定是 1 → 4 → 3 → 2,因为:
- 1 和 4 是同步代码,直接进栈执行
- setTimeout 回调进宏任务队列,排在本轮之后
- Promise.then 进微任务队列,本轮宏任务结束后立刻执行
浏览器与 Node.js 的事件循环有本质区别
不要把浏览器逻辑直接套用到服务端:
- 浏览器没有明确的“阶段”划分,主要靠宏/微任务+渲染节奏驱动
- Node.js 将事件循环分为 timers、pending callbacks、idle/prepare、poll、check、close callbacks 六个阶段,每个阶段后都会清空微任务队列
- 比如 setImmediate 和 setTimeout(fn, 0) 在 Node.js 中执行顺序不稳定,但在浏览器中 setImmediate 根本不存在
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











