事件循环讲了 100 遍,这次你终于能真的搞懂了

📌 本文是《前端打怪笔记》系列第 1 篇,适合用于复习、输出、准备面试


✅ 写在前面:你不是不懂,是没真正吃透

事件循环 几乎是前端人绕不过的坎,但也总是似懂非懂。

你可能已经:

  • 看了 5 张不同的流程图
  • 刷了 3 遍教学视频
  • 背过“宏任务和微任务”规则

但真正遇到面试题,比如:

Promise.thensetTimeout 谁先执行?
async/await 究竟属于哪类任务?
process.nextTickqueueMicrotask 谁优先?

脑子还是“嗡”的一下。

这篇文章就是为了让你 真正理解,而不是记答案。


🧠 一句话解释事件循环:JS 是单线程,但支持异步

JavaScript 是 单线程运行时:一次只能执行一个任务。

但我们又能写异步逻辑:比如定时器、网络请求、用户操作。JS 怎么做到“非阻塞”呢?

答案就是:事件循环机制(Event Loop)

那它是怎样执行的呢?

JS 引擎从任务队列中不断地调度任务 —— 执行一个宏任务 → 清空微任务 → 继续下一轮宏任务。

  • 主线程:JS 的唯一运行通道,只能同时跑一个任务。
  • 执行栈:任务正在执行时,代码的“调用路径”。
  • 任务队列:未来要执行的代码排队等待。

主线程中,存在:

  • 同步任务:立即执行的代码(如 console.log()
  • 异步任务:不会立即执行,注册后进入 任务队列 等待,由浏览器安排时间再执行(如 setTimeout()

所有异步任务都要依赖事件循环调度!

一图看懂 JS 执行顺序:

在这里插入图片描述

那么哪些是宏任务、哪些又是微任务呢?

🧱 宏任务 vs 微任务

类型常见任务来源什么时候执行
宏任务setTimeoutsetIntervalUI 渲染MessageChannel一轮事件循环的起点
微任务Promise.thenqueueMicrotaskMutationObserver当前宏任务执行完立即执行

记住:一个宏任务执行完 → 清空所有微任务 → 执行下一个宏任务

⚙️ 那 Web Worker 属于什么任务?

直接给结论,Web Worker 属于独立线程运行,不占用主线程时间。

但是需要注意:

Worker发回的消息会被主线程当作 普通的宏任务 处理(和setTimeout同级)

// 主线程
console.log("1. 开始炒菜");
const worker = new Worker("worker.js");
worker.postMessage("切土豆丝"); // 让Worker去干活

worker.onmessage = (e) => {
  console.log("4. 收到切好的土豆丝"); // 主线程在宏任务中处理消息
};

console.log("2. 炒菜中...");
setTimeout(() => { console.log("3. 定时器任务"); }, 0);

// worker.js
self.onmessage = (e) => {
  // 模拟耗时计算
  for (let i=0; i<1e9; i++) {} // Worker线程卡死,但主线程不卡!
  self.postMessage("切好了");
};

输出顺序:
1 → 2 → 3 → 4(Worker计算耗时,但主线程不等待,先执行其他宏任务)

✍️ 举几个栗子彻底理解输出顺序

1. 来个简单的练练手

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

输出顺序: 1 → 4 → 3 → 2

执行过程:

  1. 执行同步任务:console.log(1)
  2. 注册 setTimeout(宏任务),加入任务队列
  3. Promise.then() 加入微任务队列
  4. 执行同步任务:console.log(4)
  5. 清空微任务:输出 3
  6. 下一轮宏任务:输出 2

2. async/await 属于什么任务?

async function test() {
  console.log('a');
  await Promise.resolve();
  console.log('b');
}
test();
console.log('c');

输出顺序: a → c → b

拆解说明:

  • await 会将 console.log('b') 拆成一个微任务
  • 所以整个 async 函数执行到 await 暂停,主线程继续跑 console.log('c')
  • 主线程执行完后 → 微任务触发 → 输出 b

3. 来道高频面试题试试手

先不看答案自己尝试解答

console.log('script start');

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

Promise.resolve()
  .then(() => {
    console.log('promise1');
    return Promise.resolve();
  })
  .then(() => {
    console.log('promise2');
  });

queueMicrotask(() => {
  console.log('queueMicrotask');
});

console.log('script end');

拆解过程:

  • 先执行同步任务 console.log('script start')
  • 注册 setTimeout(宏任务),加入任务队列
  • Promise.then() 加入微任务队列(promise1
  • promise2 属于 promise1 微任务执行完成后才执行,所以先观察下面还有没有别的微任务
  • queueMicrotask 加入微任务队列
  • 执行同步任务 console.log('script end')

执行结果:

script start  
script end  
promise1  
queueMicrotask  
promise2  
setTimeout

你有没有答对呢…

💬 面试怎么答才显得专业?

❓ 面试官:“你能解释下 JS 的事件循环吗?”

你可以这样答:

  • 事件循环是 JS 控制异步执行顺序的机制。
  • 所有任务分为同步任务和异步任务。异步任务进一步划分为宏任务和微任务。
  • JS 每一轮事件循环会执行一个宏任务,执行完后清空所有微任务。
  • 微任务优先于下一轮宏任务执行,所以 Promise.then 总是先于 setTimeout。
  • async/await 本质上是 Promise 的语法糖,会拆成两个微任务。

下次在面试中再被问到就应该 自信吟唱 了。😄

📌 小结:写给自己复习用

知识点要点
事件循环机制宏任务 → 微任务 → 下一轮宏任务
同步任务立即执行的代码
异步任务注册进队列等待(分宏/微任务)
微任务Promise.thenqueueMicrotask,总是在宏任务后立即执行
async/await会被拆成 .then() 形成的微任务
Web Worker提供多线程能力,但与主线程隔离,靠 postMessage 通信
面试答法清晰区分同步/异步 + 宏/微任务 + 举例输出顺序

如果你觉得这篇文章对你有帮助,欢迎点赞 👍、收藏 ⭐、评论 💬 让我知道你在看!
后续我也会持续输出更多 前端打怪笔记系列文章,敬请期待!❤️

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值