📢 欢迎来到万事技术论坛!本站仅讨论合法编程技术话题,严禁外挂/作弊/黑产/盗版内容,违者封号。

精华事件循环讲透了:为什么 setTimeout(fn, 0) 不是立刻执行

yuki 进阶会员

面试高频题,但很多人只是背了结论。用一个例子把整条链路串起来:

console.log('1');

setTimeout(() => console.log('2'), 0);

Promise.resolve().then(() => console.log('3'));

console.log('4');

输出是 1 4 3 2,不是 1 2 3 4。

执行顺序的本质

JS 是单线程的,靠事件循环处理异步。每轮循环:

  1. 执行同步代码(调用栈里的)
  2. 清空微任务队列(microtask):Promise.then、queueMicrotask、MutationObserver
  3. 取一个宏任务(macrotask)执行:setTimeout、setInterval、IO、UI 渲染
  4. 回到第 2 步

关键:微任务优先级高于宏任务,且要一次性清空。

上面例子:1、4 同步打印;setTimeout 进宏任务队列;Promise.then 进微任务队列;同步代码跑完 → 清空微任务打印 3 → 下一轮取宏任务打印 2。

一个真会踩的坑

在微任务里递归创建微任务,会把事件循环饿死,页面永远卡住:

// 危险:页面直接假死,渲染永远不会执行
function loop() { Promise.resolve().then(loop); }
loop();

同样的递归用 setTimeout(loop, 0) 就没事,因为每轮只取一个宏任务,浏览器有机会插入渲染。

async/await 在哪一层

await 后面的代码等价于放进 Promise.then,属于微任务:

async function f() {
  console.log('a');
  await null;
  console.log('b');   // 微任务
}

记住这个就够了:同步 > 微任务(全清)> 宏任务(一次一个)。

楼主 · 2026-09-28 13:21 · 浏览 4
lin_dev 活跃会员

那个微任务饿死页面的例子建议每个人都试一遍,印象深刻。加一句:await 后面的代码是微任务,但如果 await 的是一个已经 resolved 的 promise,也只会在当前同步代码跑完后执行。

1楼 · 2026-09-28 13:21
登录 后即可参与回复。