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');

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

相关推荐
codingPower2 小时前
制作ftl文件通过FreeMarke生成PDF文件(含图片处理)
java·开发语言·pdf
CQ_YM2 小时前
Linux线程控制
linux·c语言·开发语言·线程
lingran__2 小时前
C语言字符函数和字符串函数详解
c语言·开发语言
CodeCraft Studio2 小时前
JavaScript图表库 DHTMLX Diagram 6.1 重磅发布:全新PERT模式上线,项目可视化能力再升级!
开发语言·javascript·ecmascript·dhtmlx·图表开发·diagram·javascript图表库
Dxy12393102162 小时前
Python的正则表达式如何做数据校验
开发语言·python·正则表达式
UP_Continue2 小时前
C++--右值和移动语义
开发语言·c++
shuaijie05183 小时前
当表格数据量过大的时候,如何使用不分页进行展示
javascript·vue.js·ecmascript
222you3 小时前
Java线程的三种创建方式
java·开发语言
云上漫步者3 小时前
深度实战:Rust交叉编译适配OpenHarmony PC——unicode_width完整适配案例
开发语言·后端·rust·harmonyos