JavaScript事件循环:餐厅里的“宏任务”与“微任务”

欢迎使用我的小程序👇👇👇👇 俱好用助手功能介绍


大家好!今天我们来聊聊JavaScript中那个让人又爱又恨的话题------事件循环中的宏任务微任务。别担心,我不会用那些晦涩难懂的专业术语轰炸你,而是用一个超有意思的比喻带你理解这个概念!

一个让人困惑的例子

先看这段代码,猜猜它会按什么顺序输出?

javascript 复制代码
console.log('1. 开始点餐');

setTimeout(() => {
  console.log('6. 20分钟后,主菜来了');
}, 0);

Promise.resolve().then(() => {
  console.log('4. 先上开胃小菜');
});

console.log('2. 点完餐了');

Promise.resolve().then(() => {
  console.log('5. 再上汤品');
});

console.log('3. 等待上菜');

// 实际输出顺序:
// 1. 开始点餐
// 2. 点完餐了
// 3. 等待上菜
// 4. 先上开胃小菜
// 5. 再上汤品
// 6. 20分钟后,主菜来了

咦?setTimeout不是设置0毫秒吗,为什么最后才执行?这就是宏任务和微任务在搞鬼!

餐厅比喻:理解事件循环

想象一下JavaScript引擎就像一家餐厅:

主厨(JavaScript引擎) 只能一次做一件事,但他有个聪明的系统来处理所有订单。

点餐台(调用栈) 是主厨当前正在做的菜,一次只能做一道。

等待区(任务队列) 是所有等待被做的菜,分为两个区域:

  • VIP区(微任务队列):开胃小菜、汤品等优先处理的
  • 普通区(宏任务队列):主菜、甜点等稍后处理的

什么是宏任务和微任务?

宏任务(主菜)

就像餐厅的主菜,需要更多准备时间:

  • setTimeout / setInterval(定时器)
  • setImmediate(Node.js环境)
  • I/O操作(如读取文件)
  • UI渲染(浏览器中)
  • 整体的script代码

微任务(开胃菜)

像餐厅的餐前小点,快速准备,优先上桌:

  • Promise.then() / .catch() / .finally()
  • process.nextTick()(Node.js环境)
  • MutationObserver(浏览器API)

事件循环的工作流程

让我用更直观的方式展示:

javascript 复制代码
// 事件循环的简化版流程
while (true) {
  // 1. 执行当前宏任务
  执行当前任务();
  
  // 2. 执行所有微任务
  while (微任务队列中有任务) {
    执行微任务();
  }
  
  // 3. 如果需要,渲染UI(浏览器中)
  
  // 4. 从宏任务队列取下一个任务
  开始下一个宏任务();
}

实战理解:点餐流程详解

让我们回到最初的例子,看看具体发生了什么:

javascript 复制代码
// 第一轮:执行整体script(这是一个宏任务)
console.log('1. 开始点餐'); // 直接执行

setTimeout(() => {
  console.log('6. 20分钟后,主菜来了');
}, 0); // 回调函数放入宏任务队列

Promise.resolve().then(() => {
  console.log('4. 先上开胃小菜');
}); // 回调函数放入微任务队列

console.log('2. 点完餐了'); // 直接执行

Promise.resolve().then(() => {
  console.log('5. 再上汤品');
}); // 回调函数放入微任务队列

console.log('3. 等待上菜'); // 直接执行

// 当前宏任务执行完毕!
// 开始检查微任务队列...

// 第二轮:执行所有微任务
// 执行第一个Promise回调:输出"4. 先上开胃小菜"
// 执行第二个Promise回调:输出"5. 再上汤品"
// 微任务队列清空!

// 第三轮:执行下一个宏任务
// 执行setTimeout回调:输出"6. 20分钟后,主菜来了"

有趣的进阶例子

javascript 复制代码
console.log('客人入座');

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

Promise.resolve()
  .then(() => {
    console.log('开胃菜');
    return Promise.resolve();
  })
  .then(() => {
    console.log('汤品');
    setTimeout(() => console.log('甜点'), 0);
  });

console.log('点餐完成');

// 输出顺序:
// 客人入座
// 点餐完成
// 开胃菜
// 汤品
// 主菜
// 甜点

注意:即使setTimeout在微任务中被设置,它仍然是宏任务,要等到下一轮!

为什么这样设计?

JavaScript是单线程的,如果没有这种优先级机制,用户界面会经常"卡住"。微任务机制允许高优先级任务(如Promise回调)插队执行,确保快速响应。

想象一下:如果你在网页上点击一个按钮,它触发的Promise回调(微任务)需要等到所有setTimeout(宏任务)都执行完才能响应,用户体验会有多糟糕!

记忆技巧

  1. 同步代码 > 微任务 > 宏任务
  2. 每个宏任务 执行完后,都会清空整个微任务队列
  3. 微任务中可以产生新的微任务,但要注意避免无限循环!

总结

记住这个简单的规律:

  • 宏任务:像餐厅的主菜,按顺序一道道上
  • 微任务:像餐前小点,总是在主菜之前全部上完
  • 事件循环:主厨总是先做完当前菜,然后处理所有小点,再做下一道主菜

理解宏任务和微任务,能帮你避免很多异步编程的坑,写出更可预测的代码。下次看到奇怪的执行顺序时,想想餐厅的比喻吧!

希望这篇文章能帮你理清这个JavaScript的重要概念!如果有疑问或想分享你的理解,欢迎在评论区讨论~ 🍽️✨


小测试:你能预测下面代码的输出顺序吗?

javascript 复制代码
console.log('1');

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

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

console.log('5');

把你的答案写在评论区吧!😉

相关推荐
黎明初时1 天前
React基础框架搭建8-axios封装与未封装,实现 API 请求管理:react+router+redux+axios+Tailwind+webpack
javascript·react.js·webpack
半夏知半秋1 天前
rust学习-Option与Result
开发语言·笔记·后端·学习·rust
淺川之夏1 天前
abstract 类,里面引用@Autowired ,使用注入类的方法,报空指针异常
java·开发语言
计算衎1 天前
Window下关于robocopy命令的讲解以及和Copy命令的区别
开发语言·bat
小此方1 天前
Re: 从零开始的C++ 入門(十)类和对象·最终篇下:类型转换、static成员、友元、匿名对象、内部类、拷贝编译优化
开发语言·c++·底层
南桥几晴秋1 天前
QT按钮控件
开发语言·qt
xj7573065331 天前
《python web开发 测试驱动方法》
开发语言·前端·python
CSDN_RTKLIB1 天前
inline内联函数基础知识
开发语言·c++
No0d1es1 天前
2025年12月 GESP CCF编程能力等级认证Python四级真题
开发语言·python·青少年编程·等级考试·gesp·ccf
love530love1 天前
EPGF 新手教程 13在 PyCharm(中文版 GUI)中创建 Hatch 项目环境,并把 Hatch 做成“项目自包含”(工具本地化为必做环节)
开发语言·ide·人工智能·windows·python·pycharm·hatch