欢迎使用我的小程序👇👇👇👇 俱好用助手功能介绍
大家好!今天我们来聊聊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)
事件循环的工作流程
让我用更直观的方式展示:
// 事件循环的简化版流程while(true){// 1. 执行当前宏任务执行当前任务();// 2. 执行所有微任务while(微任务队列中有任务){执行微任务();}// 3. 如果需要,渲染UI(浏览器中)// 4. 从宏任务队列取下一个任务开始下一个宏任务();}实战理解:点餐流程详解
让我们回到最初的例子,看看具体发生了什么:
// 第一轮:执行整体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分钟后,主菜来了"有趣的进阶例子
console.log('客人入座');setTimeout(()=>console.log('主菜'),0);Promise.resolve().then(()=>{console.log('开胃菜');returnPromise.resolve();}).then(()=>{console.log('汤品');setTimeout(()=>console.log('甜点'),0);});console.log('点餐完成');// 输出顺序:// 客人入座// 点餐完成// 开胃菜// 汤品// 主菜// 甜点注意:即使setTimeout在微任务中被设置,它仍然是宏任务,要等到下一轮!
为什么这样设计?
JavaScript是单线程的,如果没有这种优先级机制,用户界面会经常"卡住"。微任务机制允许高优先级任务(如Promise回调)插队执行,确保快速响应。
想象一下:如果你在网页上点击一个按钮,它触发的Promise回调(微任务)需要等到所有setTimeout(宏任务)都执行完才能响应,用户体验会有多糟糕!
记忆技巧
- 同步代码 > 微任务 > 宏任务
- 每个宏任务执行完后,都会清空整个微任务队列
- 微任务中可以产生新的微任务,但要注意避免无限循环!
总结
记住这个简单的规律:
- 宏任务:像餐厅的主菜,按顺序一道道上
- 微任务:像餐前小点,总是在主菜之前全部上完
- 事件循环:主厨总是先做完当前菜,然后处理所有小点,再做下一道主菜
理解宏任务和微任务,能帮你避免很多异步编程的坑,写出更可预测的代码。下次看到奇怪的执行顺序时,想想餐厅的比喻吧!
希望这篇文章能帮你理清这个JavaScript的重要概念!如果有疑问或想分享你的理解,欢迎在评论区讨论~ 🍽️✨
小测试:你能预测下面代码的输出顺序吗?
console.log('1');setTimeout(()=>console.log('2'),0);Promise.resolve().then(()=>console.log('3')).then(()=>console.log('4'));console.log('5');把你的答案写在评论区吧!😉