跳到主要内容
← 返回文章列表
面试题-基础2026/08/06约 2 分钟读完

面试题:JavaScript 事件循环

JavaScript 事件循环:微任务、宏任务、同步任务

面试题:JavaScript 事件循环
文章目录 4

面试题:JavaScript 事件循环

下面代码的输出顺序是什么?请解释原因。

console.log('1');

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

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

console.log('5');

回答时说明三个点即可:

  1. 最终输出顺序;

  2. 同步任务、微任务和宏任务分别有哪些;

  3. 为什么 setTimeout(..., 0) 不是立即执行。

我的回答

1 5 2 3 4
同步任务:

console.log('1');
console.log('5');

宏任务:

console.log('1');
console.log('5');
Promise.resolve()
  .then(() => {
    console.log('3');
  })
  .then(() => {
    console.log('4');
  });

微任务:

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

因为setTimeout(..., 0)它是异步任务,属于微任务序列

点评

正确输出顺序是:

1
5
3
4
2

定时器任务不属于微任务,setTimeout 的回调属于宏任务。

执行过程:

  1. 执行同步代码,输出 1。

  2. setTimeout 注册一个宏任务。

  3. 第一个 .then() 注册一个微任务。

  4. 继续执行同步代码,输出 5。

  5. 同步代码结束,清空微任务队列:

    • 第一个 .then() 输出 3。

    • 它执行完成后,第二个 .then() 进入微任务队列,随后输出 4。

  6. 微任务队列清空后,执行定时器宏任务,输出 2。

可以记为:

当前同步代码
→ 清空所有微任务
→ 执行下一个宏任务
→ 再次清空微任务

常见分类:

类型

示例

同步任务

普通函数、console.log

微任务

Promise.then/catch/finally、queueMicrotask

宏任务

setTimeout、setInterval、I/O、用户交互事件

setTimeout(fn, 0) 中的 0 只是表示达到最短延迟后可以进入可执行状态,不代表马上执行。

为什么回再次清空微任务

因为事件循环的规则不是“只执行一次微任务”,而是:

每完成一个宏任务,就持续清空微任务队列,直到队列为空,再执行下一个宏任务。

在这段代码中:

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

执行过程如下:

同步代码结束
微任务队列:[输出 3]

执行第一个 then
→ 输出 3
→ 第一个 then 返回的 Promise 完成
→ 第二个 then 被加入微任务队列

微任务队列:[输出 4]

继续清空微任务队列
→ 输出 4

微任务队列为空
→ 执行 setTimeout
→ 输出 2

所以这里并不是离开微任务阶段后“再次进入”,而是在同一次微任务检查中,继续执行新加入的微任务。

简化规则:

执行一个宏任务
→ 清空微任务,包括期间新增的微任务
→ 浏览器可能进行渲染
→ 执行下一个宏任务

这样设计可以让 Promise 的连续处理尽快完成。不过如果微任务不断产生新的微任务,宏任务和页面渲染就可能长期得不到执行,这叫做微任务饥饿:

function loop() {
  queueMicrotask(loop);
}

loop();

setTimeout(() => {
  console.log('可能一直无法执行');
}, 0);

评论

0 条

评论会在审核后展示,邮箱不会公开。

正在确认登录状态

5000 / 5000