← [ BACK_TO_NOTES ]

浏览器环境下的 JS Event Loop

解释浏览器端 JavaScript Event Loop 中 task、microtask checkpoint、run-to-completion 与 rendering opportunity 的关系,并澄清同步/异步不是任务队列类型。

#concept #evergreen #dev #javascript #browser

[!info] related notes

浏览器环境下的 JS Event Loop

一句话定义

浏览器 Event Loop 协调主线程上的 JavaScript 与页面更新。一个实用且更准确的模型是:

选择一个 task
  ↓
执行其中的 JavaScript(run-to-completion)
  ↓
microtask checkpoint:持续清空 microtask queue
  ↓
浏览器可能获得 rendering opportunity
  ↓
选择下一个 task

两套不能混用的分类

同步 / 异步描述工作如何完成

setTimeout(fn, 1000) 这个函数调用本身同步执行,它只向宿主注册 timer;达到时间阈值后,fn 才成为可调度的 task。

因此不要记成:

异步代码 → 立刻进入任务队列

更准确的是:

注册异步操作
→ 浏览器 / 操作系统等待或处理
→ 条件满足
→ 对应 JavaScript 回调被安排为 task 或 microtask

task / microtask 描述回调如何被调度

同步代码不是第三种队列。当前 task 被选中后,其中可立即执行的 JavaScript 进入调用栈,并持续运行到这段 JavaScript 返回。

Task 是什么

task 是浏览器事件循环的一次主要工作单元。很多教程称它为 macrotask,但正式解释时优先说 task。

常见 task 来源:

  • 初始 <script>;
  • setTimeout / setInterval 回调;
  • 用户输入和 DOM 事件回调;
  • 消息及部分网络 / I/O 回调。

规范中存在不同 task source,并不是只有一个抽象的“宏任务队列”。普通面试无需背 task source 调度细节,但不要把所有任务想成一个全局 FIFO。

Microtask checkpoint

常见 microtask 来源:

  • Promise.then / catch / finally;
  • queueMicrotask();
  • MutationObserver。

当前 JavaScript task 结束且调用栈为空后,浏览器会执行 microtask checkpoint。执行 microtask 时新加入的 microtask 也会在进入下一个 task 前继续执行,所以递归 microtask 可能长期推迟渲染和用户事件。

最小例子

console.log('A')

setTimeout(() => console.log('B'), 0)

Promise.resolve().then(() => console.log('C'))

console.log('D')
当前 script task:A → 注册 timer → 注册 Promise reaction → D
microtask checkpoint:C
后续 timer task:B

输出:

A
D
C
B

这里不是“Promise 天生比 timer 优先”,而是 Promise reaction 被安排进当前 task 结束后的 microtask checkpoint,timer callback 则要等后续 task。

Rendering opportunity

浏览器还要处理 style、layout、paint、composite 和用户输入。因此每轮 task 与 microtask checkpoint 后,浏览器可能获得一次渲染机会。

“可能”很重要:浏览器不保证每个 task 后都立即绘制。requestAnimationFrame 与渲染时机绑定,也不应简单等同于 setTimeout。

为什么“同步 → 微任务 → 宏任务”不是最终模型

这个口诀只能帮助解最简单输出题,容易制造三个误解:

  1. 误以为存在“同步任务队列”;
  2. 误以为浏览器只有一个宏任务队列;
  3. 忽略 rendering opportunity 和 microtask starvation。

最终应记:

同步 / 异步描述工作如何完成;task / microtask 描述回调回来后如何调度。一个 task 中的 JavaScript run-to-completion,随后执行 microtask checkpoint,浏览器才可能渲染并选择下一个 task。

常见误区

JavaScript 单线程 = 浏览器单线程

错误。主线程上的 JavaScript 通常 run-to-completion,但浏览器整体还会使用网络线程、渲染线程、Worker 和操作系统能力。

setTimeout(fn, 0) = 立即执行

错误。0 只表示满足最小延迟后可被调度,还要等待当前 JavaScript、microtasks 和浏览器调度。

Promise 是整体异步执行

要拆开看:new Promise(executor) 的 executor 同步执行;.then() reaction 在 Promise settle 后作为 microtask 运行。

microtask 是“更高优先级的宏任务”

不准确。microtask 有独立的 checkpoint 规则,并在进入下一个 task 前持续清空。

面试回答

浏览器 Event Loop 主要协调 task、microtask checkpoint 和页面渲染。Event Loop 选择一个 task 后,其中的 JavaScript 在调用栈上 run-to-completion;栈清空后执行 microtask checkpoint,把当前排队的 Promise reaction、queueMicrotask 等持续清空,然后浏览器可能获得一次渲染机会,再选择下一个 task。同步/异步与 task/microtask 不是同一个分类维度,所以我不会只用“同步、微任务、宏任务”这个口诀解释完整模型。

创建于 2026/9/2 更新于 2026/9/30
Digital Biome
Access

键盘导航

j / k / ↑ / ↓
选择对象
Enter / o
打开选择
/ 或 Ctrl+K
全站搜索
i / f / 1–4
筛选当前列表
g → h / l / p / t / a
首页 / 笔记 / 项目 / 工具 / About
r → 1–9
打开所选对象的关联
u
返回上一页
Esc
关闭浮层 / 取消命令;输入框内清空文本