可视化JavaScript事件循环原理
前言
众所周知,JavaScript是单线程的,它一次只能处理一个任务。如果所有的代码运行时都需要等待前一个代码块运行完成才能执行,那对于i/o密集型任务来说时间带来的消耗是无法想象的,由此引出了JavaScript事件循环概念,通过JavaScript事件循环,我们能够智能地对任务调度做出合理规划,不会让整个程序系统看起来像”崩了“一般静止不动。
英文术语解析:
JavaScript RunTime:JavaScript运行时
JavaScript Engine:JavaScript引擎
heap:内存堆
call stack:调用栈
web APIs:网络接口
event loop:事件循环
task queue:宏任务队列
microtask queue:微任务队列
一 普通任务的调用方式

二 web APIs的具体形式

三 web APIs与引擎、设备交互

四 具体的宏任务有哪些
常见的宏任务有setTimeout、setInterval、 xhr.onload、I/O、UI渲染等。

五 web APIs与宏任务队列的事件循环过程

六 具体的微任务有哪些
微任务仅由promise的回掉.then、await之后的异步函数体(不包括await 同行)、queueMicrotask()、MutationObserver、process.nextTick()(Node.js 特有)等。

七 微任务队列的事件循环过程

八 微任务与宏任务的关系
事件循环优先处理微任务队列内的任务而不是宏任务队列的任务,当宏任务队列进行时,会检查微任务队列内的任务是否存在,有就会优先执行微任务。

九 特殊情况-无限微任务循环

十 小结
请分析下面代码块的输出结果,验证你的学习成果,蒙住答案,好好思考一下。
题目1:
1 | |
答案:51342
解析:
代码自上而下开始执行:
首先,Promise.resolve().then 率先注册了一个微任务 1;接着 setTimeout 开启 10ms 定时器准备推入宏任务;随后 queueMicrotask 将回调函数 3 推入微任务队列;最后执行同步代码,直接打印 5。
同步代码执行完毕后,事件循环开始清空微任务队列:
率先取出最先排队的微任务,打印 1;紧接着取出第二个微任务,打印 3。在此过程中,内部的 queueMicrotask 又将 4 动态推入了当前的微任务队列。由于事件循环会一次性连续清空所有微任务(包含微任务衍生出的新微任务),因此不会停顿,直接继续取出新微任务并打印 4。
当微任务队列彻底清空后,事件循环进入宏任务阶段,执行到期的定时器回调,最终打印 2。
题目2:
1 | |
答案:
1 | |
解析:整体 script 作为第一个宏任务进入主线程,代码自上而下开始执行同步任务:
首先,直接输出 script start;遇到 setTimeout 时,将其回调推入宏任务队列;接着调用 async1(),输出 async1 start。在遇到 await async2() 时,由于 async/await 本质是 Promise + Generator 的语法糖,它会先同步执行 async2() 并输出 async2,随后让出线程,将 await 后续的代码包装成微任务放入微任务队列,并跳出 async1 函数。
继续向下执行,遇到 new Promise 立即执行其构造函数,输出 promise1,并将 .then() 中的回调推入微任务队列;随后执行完最后一行同步代码,输出 script end。
此时主线程空闲,事件循环开始依次清空微任务队列,先后输出 async1 end 和 promise2。本轮微任务清空完毕后,再进入下一轮宏任务,读取并执行 setTimeout 的回调,最终输出 setTimeout。
如有疑问欢迎在评论区留言!期待你的评论!
十一 参考文献
JavaScript Visualized - Event Loop, Web APIs, (Micro)task Queue
深入理解 JavaScript 之事件循环(Event Loop)
事件循环:微任务和宏任务
十二 GIF制作工具
Made by Eli Bi