前端人搞不清任务队列?3分钟看懂事件循环里的宏任务微任务(附避
- 前端人搞不清任务队列?3分钟看懂事件循环里的宏任务微任务(附避坑指南)
- 为啥我写的 setTimeout 总是比 Promise 慢半拍?
- 事件循环听着高大上,其实它就是个“打工人调度器”
- 宏任务家族都有谁:script、setTimeout、setInterval、I/O、UI 渲染…
- 微任务天团成员:Promise.then、queueMicrotask、MutationObserver、process.nextTick(Node 专属)
- 宏任务和微任务的执行顺序到底怎么跑?
- UI 渲染到底在哪个环节发生?requestAnimationFrame 又插在哪?
- 经典的“我以为先执行”翻车现场
- 1. setTimeout(fn, 0) 真的是立刻执行吗?
- 2. Promise 构造函数是同步的,.then 才是异步的
- 3. Vue 的 nextTick 到底用了啥?
- 遇到诡异时序问题怎么排查?
- 1. console.log 大法好,但得打在哪一层
- 2. performance.mark + measure 精准定位
- 3. Chrome DevTools Performance 面板
- 写代码时怎么利用任务机制优化体验?
- 1. 把耗时计算拆到多个宏任务,避免页面卡死
- 2. 用 queueMicrotask 替代 Promise.resolve().then() 更语义化
- 3. 想在 DOM 更新后立刻操作?别乱用 setTimeout,试试 nextTick 或 rAF
- 防抖节流别只盯着时间,结合任务类型能更精准控制
- 最后唠一句:任务队列不是背八股文,是理解 JS 的呼吸节奏
前端人搞不清任务队列?3分钟看懂事件循环里的宏任务微任务(附避坑指南)
友情提示:本文全程口语化,代码管够,吐槽密集,建议收藏后对着控制台边敲边骂,效果更佳。
为啥我写的 setTimeout 总是比 Promise 慢半拍?
先甩一段“灵魂拷问”代码,欢迎对号入座:
console.log('start');setTimeout(()=>console.log('timeout'),0);Promise.resolve().then(()=>console.log('promise'));console.log('end');跑出来的顺序永远是:
start end promise timeout是不是瞬间怀疑人生:我写的 0 毫秒延时呢?Promise 凭啥插队?
别急着砸键盘,这就是宏任务(macrotask)和微任务(microtask)的“排班表”在作怪。
一句话:微任务就是 VIP 通道,宏任务是普通安检口——VIP 没走完,后面经济舱只能干瞪眼。
事件循环听着高大上,其实它就是个“打工人调度器”
JS 是单线程,像一家只有一名咖啡师的星巴克。
顾客(任务)排长队,咖啡师(主线程)一次只能做一杯。
但店里为了“用户体验”,给任务分了俩队:
- 宏任务队列:今天必须做完,但不差这几秒;
- 微任务队列:老板亲戚,不做就投诉。
咖啡师流程(事件循环伪代码):
while(queue.waitForCustomer()){constmacroTask=queue.getMacroTask();// 只取一个macroTask.run();// 做完这一单letmicroTask;while(microTask=queue.getMicroTask()){// 清空前,别下班microTask.run();}if(shouldRender())render();// 每轮宏任务后可能刷帧}看懂了没?一次事件循环 = 1 个宏任务 + 清空所有微任务 + 可能渲染。
所以 Promise.then 永远比 setTimeout 早,因为人家在 VIP 队里,setTimeout 还在经济舱排队检票。
宏任务家族都有谁:script、setTimeout、setInterval、I/O、UI 渲染…
先给宏任务“拍全家福”:
| 来源 | 典型 API | 备注 |
|---|---|---|
| 脚本 | <script> | 第一次同步执行的整段代码就是宏任务 |
| 定时器 | setTimeout / setInterval | 最小 4ms 延时(HTML 标准) |
| I/O | fetch 回来、XHR、文件读写 | 网络线程回来再排队 |
| UI | 用户交互、postMessage | 浏览器要响应点击、输入 |
| 渲染 | requestAnimationFrame | 在宏任务后、渲染前插一脚 |
代码感受下“宏任务众生相”:
// 1. script 本身console.log('script start');// 同步,第一个宏任务// 2. 定时器setTimeout(()=>console.log('setTimeout'),0);// 3. I/Ofetch('/').then(()=>console.log('fetch callback'));// 4. 用户交互button.addEventListener('click',()=>console.log('click'));// 5. UI 渲染requestAnimationFrame(()=>console.log('rAF'));微任务天团成员:Promise.then、queueMicrotask、MutationObserver、process.nextTick(Node 专属)
微任务人少,但个个都是“关系户”:
| 来源 | 典型 API | 备注 |
|---|---|---|
| Promise | .then / .catch / .finally | 标准微任务 |
| 手动 | queueMicrotask(fn) | 浏览器新 API,语义更直给 |
| DOM | MutationObserver | 监听 DOM 变动,批量微任务 |
| Node | process.nextTick | Node 里比 Promise 还 VIP,比微任务更微 |
来段“微任务全家桶”:
Promise.resolve().then(()=>console.log('promise'));queueMicrotask(()=>console.log('queueMicrotask'));constobserver=newMutationObserver(()=>console.log('mutation'));observer.observe(document.body,{childList:true});document.body.appendChild(document.createTextNode('hi'));// 触发// Node 环境if(typeofprocess!=='undefined'){process.nextTick(()=>console.log('nextTick'));}跑在浏览器里,顺序大概率:
promise queueMicrotask mutation(nextTick 只在 Node 出现,浏览器没有)
宏任务和微任务的执行顺序到底怎么跑?
再上一张“灵魂流程图”——代码版:
console.log('① 同步代码');setTimeout(()=>{console.log('② 宏任务1-setTimeout');Promise.resolve().then(()=>console.log('③ 宏任务1里产生的微任务'));},0);queueMicrotask(()=>{console.log('④ 微任务1-queueMicrotask');setTimeout(()=>console.log('⑤ 微任务里又塞了个宏任务'),0);});Promise.resolve().then(()=>console.log('⑥ 微任务2-promise'));console.log('⑦ 同步代码结束');控制台输出:
① 同步代码 ⑦ 同步代码结束 ④ 微任务1-queueMicrotask ⑥ 微任务2-promise ② 宏任务1-setTimeout ③ 宏任务1里产生的微任务 ⑤ 微任务里又塞了个宏任务看懂了没?微任务可以嵌套微任务,但一轮宏任务只能取一个。
所以千万别在微任务里写死循环,不然页面直接卡成 PPT:
functionloop(){queueMicrotask(loop);// 无限微任务,浏览器永远走不到下一帧}loop();UI 渲染到底在哪个环节发生?requestAnimationFrame 又插在哪?
很多人以为“渲染在宏任务之后”,其实更细:
在一次宏任务 + 所有微任务完成后,浏览器会看要不要刷帧。
rAF 的回调就插在“宏任务后、渲染前”这个黄金位置。
代码验证:
button.addEventListener('click',()=>{console.log('click 宏任务');Promise.resolve().then(()=>console.log('promise 微任务'));requestAnimationFrame(()=>console.log('rAF'));console.log('click 同步结束');});点击按钮,输出:
click 宏任务 click 同步结束 promise 微任务 rAF看到没?rAF 在微任务之后、渲染之前。
所以想在 DOM 更新后、渲染前做点动画,用 rAF 就对了,别傻乎乎 setTimeout。
经典的“我以为先执行”翻车现场
1. setTimeout(fn, 0) 真的是立刻执行吗?
浏览器最小延时 4ms,Node 里 1ms,还要排队。
下面这段代码,timeout 永远最后:
Promise.resolve().then(()=>console.log('then'));setTimeout(()=>console.log('timeout'),0);2. Promise 构造函数是同步的,.then 才是异步的
newPromise(resolve=>{console.log('promise ctor 同步');resolve();}).then(()=>console.log('promise then 异步'));输出:
promise ctor 同步 promise then 异步ctor 里千万别做重计算,否则照样卡线程。
3. Vue 的 nextTick 到底用了啥?
Vue2 兼容写法:
// 优先 Promise.thenif(typeofPromise!=='undefined'){Vue.nextTick=cb=>Promise.resolve().then(cb);}elseif(typeofMutationObserver!=='undefined'){// 降级 MutationObserverconstobserver=newMutationObserver(()=>{// 清空队列});}else{// 再降级 setTimeoutVue.nextTick=cb=>setTimeout(cb,0);}所以Vue.nextTick 是微任务,放心在里边读 DOM,一定比渲染早。
遇到诡异时序问题怎么排查?
1. console.log 大法好,但得打在哪一层
console.log('=== 宏任务起点 ===');queueMicrotask(()=>console.log('=== 微任务起点 ==='));给日志加“边框”,一眼看出谁在哪个队列。
2. performance.mark + measure 精准定位
performance.mark('A');setTimeout(()=>{performance.mark('B');performance.measure('A to B','A','B');console.log(performance.getEntriesByName('A to B')[0].duration);},0);能看到真实延时,而不是“我以为 0ms”。
3. Chrome DevTools Performance 面板
- 录一段交互;
- 看 Main 线程火焰图,红色三角就是宏任务,紫色小块是微任务;
- 对照帧率,一眼看出谁在掉帧。
写代码时怎么利用任务机制优化体验?
1. 把耗时计算拆到多个宏任务,避免页面卡死
constbigArray=newArray(10_000_000).fill(0).map((_,i)=>i);functionchunk(arr,size=1000){leti=0;functiondoChunk(){constend=Math.min(i+size,arr.length);for(;i<end;i++){// 重计算arr[i]=arr[i]**2;}if(i<arr.length){setTimeout(doChunk,0);// 让出主线程,宏任务续命}}doChunk();}chunk(bigArray);每 1000 条让一次座,页面还能滚动。
2. 用 queueMicrotask 替代 Promise.resolve().then() 更语义化
// 以前Promise.resolve().then(()=>console.log('next'));// 现在queueMicrotask(()=>console.log('next'));少了创建 Promise 对象的开销,代码更直给。
3. 想在 DOM 更新后立刻操作?别乱用 setTimeout,试试 nextTick 或 rAF
// Vue 场景this.msg='新消息';this.$nextTick(()=>{// DOM 已更新,可以安心量高度console.log(this.$refs.box.scrollHeight);});原生场景:
requestAnimationFrame(()=>{// 渲染前最后一站console.log('DOM 马上要刷了');});防抖节流别只盯着时间,结合任务类型能更精准控制
传统节流:
functionthrottle(fn,wait){letlast=0;returnfunction(...args){constnow=Date.now();if(now-last>wait){last=now;fn.apply(this,args);}};}但高刷屏幕 120Hz,16ms 一帧,定时器最小 4ms,可能一次刷两帧。
用 rAF 做“帧同步节流”更丝滑:
functionrafThrottle(fn){letlocked=false;returnfunction(...args){if(locked)return;locked=true;requestAnimationFrame(()=>{fn.apply(this,args);locked=false;});};}无论 60Hz 还是 120Hz,都一帧只跑一次,不掉帧、不浪费。
最后唠一句:任务队列不是背八股文,是理解 JS 的呼吸节奏
写前端就像打鼓,宏任务是“咚”,微任务是“哒”,渲染是“次”。
鼓点乱了,用户就觉得卡;节奏对了,页面才能跟着心跳走。
下回再看到 setTimeout 被 Promise 插队,别骂娘——泡杯茶,打开控制台,对着 event loop 的鼓点敲代码,你就是乐队指挥。
(全文完,键盘已冒烟,我去加散热硅脂了。)
欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。
推荐:DTcode7的博客首页。
一个做过前端开发的产品经理,经历过睿智产品的折磨导致脱发之后,励志要翻身农奴把歌唱,一边打入敌人内部一边持续提升自己,为我们广大开发同胞谋福祉,坚决抵制睿智产品折磨我们码农兄弟!
| 专栏系列(点击解锁) | 学习路线(点击解锁) | 知识定位 |
|---|---|---|
| 《微信小程序相关博客》 | 持续更新中~ | 结合微信官方原生框架、uniapp等小程序框架,记录请求、封装、tabbar、UI组件的学习记录和使用技巧等 |
| 《AIGC相关博客》 | 持续更新中~ | AIGC、AI生产力工具的介绍,例如stable diffusion这种的AI绘画工具安装、使用、技巧等总结 |
| 《HTML网站开发相关》 | 《前端基础入门三大核心之html相关博客》 | 前端基础入门三大核心之html板块的内容,入坑前端或者辅助学习的必看知识 |
| 《前端基础入门三大核心之JS相关博客》 | 前端JS是JavaScript语言在网页开发中的应用,负责实现交互效果和动态内容。它与HTML和CSS并称前端三剑客,共同构建用户界面。 通过操作DOM元素、响应事件、发起网络请求等,JS使页面能够响应用户行为,实现数据动态展示和页面流畅跳转,是现代Web开发的核心 | |
| 《前端基础入门三大核心之CSS相关博客》 | 介绍前端开发中遇到的CSS疑问和各种奇妙的CSS语法,同时收集精美的CSS效果代码,用来丰富你的web网页 | |
| 《canvas绘图相关博客》 | Canvas是HTML5中用于绘制图形的元素,通过JavaScript及其提供的绘图API,开发者可以在网页上绘制出各种复杂的图形、动画和图像效果。Canvas提供了高度的灵活性和控制力,使得前端绘图技术更加丰富和多样化 | |
| 《Vue实战相关博客》 | 持续更新中~ | 详细总结了常用UI库elementUI的使用技巧以及Vue的学习之旅 |
| 《python相关博客》 | 持续更新中~ | Python,简洁易学的编程语言,强大到足以应对各种应用场景,是编程新手的理想选择,也是专业人士的得力工具 |
| 《sql数据库相关博客》 | 持续更新中~ | SQL数据库:高效管理数据的利器,学会SQL,轻松驾驭结构化数据,解锁数据分析与挖掘的无限可能 |
| 《算法系列相关博客》 | 持续更新中~ | 算法与数据结构学习总结,通过JS来编写处理复杂有趣的算法问题,提升你的技术思维 |
| 《IT信息技术相关博客》 | 持续更新中~ | 作为信息化人员所需要掌握的底层技术,涉及软件开发、网络建设、系统维护等领域的知识 |
| 《信息化人员基础技能知识相关博客》 | 无论你是开发、产品、实施、经理,只要是从事信息化相关行业的人员,都应该掌握这些信息化的基础知识,可以不精通但是一定要了解,避免日常工作中贻笑大方 | |
| 《信息化技能面试宝典相关博客》 | 涉及信息化相关工作基础知识和面试技巧,提升自我能力与面试通过率,扩展知识面 | |
| 《前端开发习惯与小技巧相关博客》 | 持续更新中~ | 罗列常用的开发工具使用技巧,如 Vscode快捷键操作、Git、CMD、游览器控制台等 |
| 《photoshop相关博客》 | 持续更新中~ | 基础的PS学习记录,含括PPI与DPI、物理像素dp、逻辑像素dip、矢量图和位图以及帧动画等的学习总结 |
| 日常开发&办公&生产【实用工具】分享相关博客》 | 持续更新中~ | 分享介绍各种开发中、工作中、个人生产以及学习上的工具,丰富阅历,给大家提供处理事情的更多角度,学习了解更多的便利工具,如Fiddler抓包、办公快捷键、虚拟机VMware等工具 |
吾辈才疏学浅,摹写之作,恐有瑕疵。望诸君海涵赐教。望轻喷,嘤嘤嘤
非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。愿斯文对汝有所裨益,纵其简陋未及渊博,亦足以略尽绵薄之力。倘若尚存阙漏,敬请不吝斧正,俾便精进!