JavaScript事件循环是协调同步任务、微任务和宏任务执行顺序的调度机制,核心为“先清微任务,再取下一个宏任务”;同步代码执行完后,先执行全部微任务,再执行一个宏任务,如此循环。

JavaScript 的事件循环不是“循环执行代码”,而是协调同步任务、微任务和宏任务执行顺序的调度机制。它让单线程的 JS 能不卡顿地处理异步操作,核心在于“先清微任务,再取下一个宏任务”这一固定节奏。
调用栈空了,事件循环才真正开始工作
所有同步代码(比如 console.log、函数调用、if 判断)都在调用栈里按顺序执行,一气呵成。只有当调用栈彻底清空,事件循环才会介入:
- 检查微任务队列(如
Promise.then、queueMicrotask),一次性全部执行完,中间不插队其他类型任务 - 微任务队列清空后,才从宏任务队列(如
setTimeout、setInterval、整个 script 标签)里取出最老的一个执行 - 这个宏任务执行完,再次清空微任务队列——然后继续取下一个宏任务
微任务永远比宏任务“抢跑”一步
哪怕 setTimeout(fn, 0) 看起来是“立刻”,它的回调仍是宏任务;而一个刚创建的 Promise.resolve().then(...) 回调却是微任务。所以:
- 同步代码输出后,微任务一定先于任何宏任务输出
- 在微任务中新建的微任务(比如
then里又写了一个then),也会被加到本轮微任务队列末尾,并在本轮内执行完 - 但微任务里新建的宏任务(比如
then里调用setTimeout),要等到下一轮事件循环才可能执行
常见任务类型要分清,别只看“谁写在前面”
执行顺序不取决于代码书写位置,而取决于任务被放入哪个队列:
立即学习“Java免费学习笔记(深入)”;
-
宏任务:整体 script、
setTimeout/setInterval回调、I/O、UI 渲染(浏览器)、postMessage -
微任务:
Promise.then/catch/finally、queueMicrotask、MutationObserver回调 -
注意:
async/await是语法糖,await后面的表达式结果决定后续代码何时进入微任务——等价于Promise.then
一个典型执行流帮你串起来
看这段代码:
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 回调进微任务队列(当前宏任务一结束就执行)
- 调用栈空了 → 执行微任务 → 输出 3 → 微任务队列清空 → 取出第一个宏任务 → 输出 2


















