JavaScript 事件循环
要点速览
shell
# 什么是事件循环
事件循环 是 JS 实现「单线程非阻塞异步」的底层运行机制
JS 主线程只有一个,靠事件循环排队、轮流执行同步、异步任务,不会卡死。
# 任务队列分类:
宏任务(macrotask,教学用划分):首轮脚本执行、setTimeout/setInterval 回调、setImmediate(Node)、MessageChannel、DOM 事件(如 click)等进入任务队列的回调。
说明:网络请求(XHR/fetch)完成后**回调**作为任务入队,不宜记成「HTTP 请求本身是宏任务」;浏览器绘制与任务/微任务的对应关系在规范里更细,此处为便于理解做了合并。
微任务(microtask):Promise.then/catch/finally、queueMicrotask、MutationObserver;`async/await` 里 **`await` 之后的代码**等价挂到 Promise 微任务。Node 另有 process.nextTick,**先于**其他微任务执行。
# 事件循环过程:宏任务、微任务、是否渲染是则渲染
[宏任务队列] → 取出一个宏任务并执行
↓
执行期间产生的微任务,暂存
↓
[微任务队列] → 清空全部
↓
[是否需要渲染?]
(约16.6ms间隔/dom变化)
↓是
--- 渲染阶段开始 (不可打断) ---
├─ 执行 requestAnimationFrame 回调(全部)
├─ 重新计算样式 (Recalculate Style)
├─ 布局 (Layout)
├─ 更新图层树
├─ 执行 ResizeObserver / IntersectionObserver 回调
├─ 绘制 (Paint)
├─ 合成 (Composite)
└─ 执行 requestIdleCallback (部分实现空闲时)
--- 渲染阶段结束 ---
↓
回到顶部,取下一个宏任务
# 注意:
JS 是单线程的,同一时间只能执行一个任务,一个宏任务在执行,另一个宏任务就需要等待,等上一个宏任务执行完,当前宏任务再进入主线程执行
所以异步任务不是单独开辟线程执行的,也就是延时定时器可能不准:需等前面的任务跑完;嵌套 setTimeout 还可能被浏览器做最小间隔(如 4ms)节流。
定时器的原理就是将代码排到时间循环之后,等主线程轮到它,即当下事件循环开启下一次的时候开始执行拓展
shell
# 宏任务和微任务坑
Node 事件循环和浏览器不一样,微任务优先级:process.nextTick > 其他所有微任务 (Promise.then),浏览器没有 nextTick。
# 渲染与任务(浏览器)
主线程跑完当前 JS、清空微任务后,浏览器可能在取下一个任务前插入样式计算、布局、绘制等(含 rAF);不必把整段绘制硬说成「一个宏任务」。
# Node 与文件 I/O
readFile/writeFile 等完成回调由 libuv 在各阶段调度;教材里常与「宏任务」类比,但与浏览器里 setTimeout 队列**不是同一套机制**,分开理解即可。
# setImmediate 与 setTimeout(0)(Node)
二者**相对顺序不固定**,与当前是否处于 I/O 回调等阶段有关,不要死记「谁一定先」;process.nextTick 则总是优先于普通微任务(Promise.then 等)。
# 微任务 MutationObserver
监听 DOM 元素变化,DOM 一改,就触发回调。
MutationObserver 是浏览器用于监听 DOM 变动的 API,其回调是微任务。
# 微任务 queueMicrotask
浏览器 + Node 都有,作用是:手动、主动往微任务队列添加一个微任务
# process.nextTick(Node环境,优先级最高)
作用是:每执行完一段代码,先清空所有 nextTick,再执行其他微任务
process.nextTick 是 Node 专属微任务,优先级最高,会优先于所有其他微任务执行。
setTimeout 1000 是1s后才放入到任务队列中执行JS 事件循环(另一稿)
要点速览
shell
# 事件循环的作用
JS是单线程,通过EventLoop实现异步执行,避免阻塞
# 什么是事件循环
在 JS 代码从上到下依次执行过程中,同步代码立即执行,异步任务不阻塞,放到任务队列;
主线程执行完所有同步代码后,循环读取任务队列执行异步代码,这个循环机制就叫事件循环。
# 执行顺序
主线程执行同步 → 执行主线程的所有微任务 → 取出一个宏任务执行 → 再执行当前宏任务下的所有微任务 → 循环往复。
# 执行优先级
同步代码 > 微任务 MicroTask > 宏任务 MacroTask
- 任务队列分类:
- 宏任务(macrotask,教学用划分):首轮脚本执行、setTimeout/setInterval 回调、setImmediate(Node)、MessageChannel、DOM 事件回调等;网络完成后的回调作为任务入队。
- 微任务(microtask):Promise.then/catch/finally、queueMicrotask、MutationObserver;`await` 后续代码等价挂到 Promise 微任务。Node 另有 process.nextTick,先于其他微任务。拓展
shell
# 宏任务和微任务坑
Node 事件循环和浏览器不一样,微任务优先级:process.nextTick > 其他所有微任务 (Promise.then),浏览器没有 nextTick。
# 渲染与任务(浏览器)
主线程跑完当前 JS、清空微任务后,浏览器可能在取下一个任务前插入样式计算、布局、绘制等(含 rAF);不必把整段绘制硬说成「一个宏任务」。
# Node 与文件 I/O
readFile/writeFile 等完成回调由 libuv 在各阶段调度;教材里常与「宏任务」类比,但与浏览器里 setTimeout 队列**不是同一套机制**,分开理解即可。
# setImmediate 与 setTimeout(0)(Node)
二者**相对顺序不固定**,与当前是否处于 I/O 回调等阶段有关,不要死记「谁一定先」;process.nextTick 则总是优先于普通微任务(Promise.then 等)。
# 微任务 MutationObserver
监听 DOM 元素变化,DOM 一改,就触发回调。
MutationObserver 是浏览器用于监听 DOM 变动的 API,其回调是微任务。
# 微任务 queueMicrotask
浏览器 + Node 都有,作用是:手动、主动往微任务队列添加一个微任务
# process.nextTick(Node环境,优先级最高)
作用是:每执行完一段代码,先清空所有 nextTick,再执行其他微任务
process.nextTick 是 Node 专属微任务,优先级最高,会优先于所有其他微任务执行。