前端必学|事件循环核心考点,一篇吃透

7 阅读3分钟

前言

在 JavaScript 异步编程中,事件循环(Event Loop) 是绕不开的核心知识点,也是前端面试高频必考题

很多同学对宏任务、微任务、async/await 执行顺序一知半解,做题频频翻车。

这篇文章我会用通俗的语言 + 清晰的规则 + 经典真题,带你彻底吃透事件循环,面试遇到直接拿捏。


一、为什么需要事件循环?

JavaScript 是单线程语言,同一时间只能执行一段代码。

但我们日常开发中,会遇到大量异步操作:

  • 定时器 setTimeout
  • 网络请求 Ajax/fetch
  • Promise 异步回调
  • DOM 渲染

为了不阻塞主线程,JS 设计了事件循环机制,用来调度同步代码与异步代码的执行顺序。

一句话概括:

事件循环 = JS 异步非阻塞的底层实现。


二、核心概念:执行栈、宏任务、微任务

1. 执行栈(Call Stack)

同步代码直接放入执行栈,先进后出,立即执行。

2. 宏任务(Macrotask)

  • 宿主环境提供(浏览器/Node.js)

  • 每次事件循环只执行一个

  • 常见宏任务:

    • 全局 script 代码
    • setTimeout / setInterval
    • I/O 操作(ajax、文件读取)
    • UI 渲染

3. 微任务(Microtask)

  • JS 引擎自身提供

  • 执行栈为空时,一次性清空整个队列

  • 优先级 远高于 宏任务

  • 常见微任务:

    • Promise.then / catch / finally
    • await 后面的代码
    • queueMicrotask()
    • MutationObserver

三、事件循环执行规则(黄金定律)

这是做题、面试永远不会错的核心规则:

  1. 先执行所有同步代码
  2. 同步执行完毕 → 清空所有微任务
  3. 微任务清空 → 执行一个宏任务
  4. 宏任务执行完毕 → 再次清空微任务
  5. 循环往复

终极口诀:

同步优先 → 微任务插队 → 宏任务最后


四、面试高频坑点(90% 人踩过)

坑 1:Promise 内部是同步执行的

console.log(1);
new Promise((resolve) => {
  console.log(2); // 同步执行!
  resolve();
}).then(() => {
  console.log(3); // 微任务
});
console.log(4);

// 输出:1 → 2 → 4 → 3

坑 2:await 后面的代码 = 微任务

async function fn() {
  console.log(1);
  await Promise.resolve();
  console.log(2); // 微任务!
}
console.log(3);
fn();
console.log(4);

// 输出:3 → 1 → 4 → 2

坑 3:await 两种关键区别(大厂必考)

这是事件循环最难、最容易错的细节:

① await 后面是普通函数

async function fn1() {
  console.log(1);
  await fn2();
  console.log(2);
}

✅ 微任务正常入队,先入队先执行。

② await 后面是 Promise

async function fn1() {
  console.log(1);
  await Promise.resolve();
  console.log(2);
}

✅ 微任务延迟入队,比后面的 .then 更晚执行。


五、经典面试真题(带解析)

真题 1(基础)

console.log(1);
setTimeout(() => console.log(2));
Promise.resolve().then(() => console.log(3));
console.log(4);

// 输出:1 → 4 → 3 → 2

真题 2(Promise 同步)

console.log('start');
new Promise((resolve) => {
  console.log('promise');
  resolve();
}).then(() => {
  console.log('then');
});
console.log('end');

// 输出:start → promise → end → then

真题 3(async/await 综合题)

async function async1() {
  console.log(1);
  await async2();
  console.log(2);
}
async function async2() {
  console.log(3);
}

console.log(4);
async1();
new Promise(resolve => {
  console.log(5);
  resolve();
}).then(() => console.log(6));
console.log(7);

// 输出:4 → 1 → 3 → 5 → 7 → 2 → 6

真题 4(终极难度)

console.log(1);
setTimeout(() => {
  console.log(2);
  Promise.resolve().then(() => console.log(3));
}, 0);
Promise.resolve().then(() => {
  console.log(4);
  setTimeout(() => console.log(5), 0);
});
console.log(6);

// 输出:1 → 6 → 4 → 2 → 3 → 5

六、一页纸总结(建议收藏)

  1. JS 单线程,依靠事件循环实现异步非阻塞
  2. 执行顺序:同步 > 微任务 > 宏任务
  3. 微任务:一次性清空;宏任务:一次执行一个
  4. Promise 内部同步,.then 是微任务
  5. await 后面的代码一律是微任务
  6. await 普通函数:微任务正常排队
  7. await Promise:微任务延迟排队

写在最后

事件循环是前端基础的重中之重,不管是日常开发还是面试,都必须牢牢掌握。

把这篇文章多看两遍,配合真题练习,你完全可以轻松应对所有事件循环相关问题。

如果对你有帮助,欢迎 点赞 + 收藏 + 关注,后续持续输出前端硬核干货~