JavaScript事件循环严格遵循“宏任务→清空微任务→渲染→下一宏任务”顺序;Promise.then属微任务,setTimeout属宏任务,故前者总先执行。
JavaScript 事件循环不是“轮询”或“定时检查”,而是一个严格遵循「宏任务 → 清空微任务 → 渲染(浏览器)→ 下一个宏任务」顺序的调度机制。它决定了 setTimeout、Promise、async/await 这些异步代码到底什么时候真正执行。
Promise.then 总比 setTimeout 先输出?因为它们分属不同队列:前者进微任务队列,后者进宏任务队列。事件循环每次只取一个宏任务执行,但执行完后**必须立刻清空全部微任务**,不等下一轮。
script(整体脚本)、setTimeout、setInterval、I/O、UI 渲染(浏览器中)Promise.then/catch/finally、queueMicrotask、async/await 的 awai
t 后续部分、MutationObserver
then 里再调一次 Promise.resolve().then),也会被追加并本轮清空async/await 在事件循环里怎么算任务?async 函数本身是宏任务(进入调用栈即开始执行),但 await 后面的表达式一旦返回 Promise,其后续逻辑就变成微任务 —— 和直接写 .then 等价。
console.log('start');
async function foo() {
console.log('foo start');
await Promise.resolve();
console.log('foo end');
}
foo();
console.log('end');
输出顺序是:start → foo start → end → foo end。注意:foo end 不是下一轮宏任务,而是本轮宏任务(foo)执行到 await 后暂停,等 Promise resolve 后,把 console.log('foo end') 推入微任务队列,紧接在 end 后执行。
setTimeout(..., 0) 并不“立刻”执行它只是把回调放进宏任务队列末尾,要等当前所有同步代码 + 所有微任务跑完,再等到下一轮事件循环才可能执行 —— 所以它永远排在所有微任务之后。
setTimeout(() => ..., 0) 能“让出线程”给 UI 更新,其实它比 queueMicrotask 还慢queueMicrotask;需要“下一帧渲染后执行”,用 requestAnimationFrame 或 setTimeout(配合 performance.now() 观察实际延迟)setTimeout 实际延迟往往 ≥ 4ms(受浏览器节流限制)最常被忽略的一点:事件循环不是“JS 引擎自己决定怎么调度”,而是由宿主环境(浏览器或 Node.js)定义任务队列的类型和优先级。比如 Node.js 有 process.nextTick(比微任务还早),而浏览器没有;MutationObserver 回调属于微任务,但它的触发时机依赖 DOM 变更检测,不是纯 JS 控制。理解这一点,才能真正看懂跨环境行为差异。