
JavaScript - 宏任务(Macrotasks)& 微任务(Microtasks)& 事件循环机制(Event Loop)
事件循环(Event Loop)
浏览器事件循环和 Node.js 的区别
浏览器和 Node.js 都使用事件循环(Event Loop)来处理异步任务,但由于运行环境不同,它们的事件循环机制在架构设计、宏任务与微任务处理、任务来源和模块支持等方面存在明显差异。
一、浏览器事件循环机制
浏览器的事件循环遵循 HTML5 标准,基本结构如下:
- 执行顺序
同步任务(调用栈)
微任务队列(Microtasks):如 Promise.then、MutationObserver
宏任务队列(Macrotasks):如 setTimeout、setInterval、requestAnimationFrame - 典型任务来源
任务类型 示例
宏任务 setTimeout、setInterval、message、UI 渲染、XHR onload
微任务 Promise.then、queueMicrotask、MutationObserver - 特点
每执行一个宏任务,立即清空所有微任务。
浏览器事件循环中含有 UI 渲染阶段,微任务清空后才允许渲染。
二、Node.js 的事件循环机制
Node.js 基于 libuv 库实现自己的事件循环,主要包含 6 个阶段,不完全等同于浏览器模型。
- Node.js 事件循环阶段
timers:处理 setTimeout、setInterval
pending callbacks:处理某些 I/O 的回调
idle, prepare:仅内部使用
poll:检索 I/O 事件
check:处理 setImmediate
close callbacks:处理 close 事件,如 socket.on('close')
每个阶段之间都会执行微任务队列。
-
微任务来源
process.nextTick()(优先级最高,不属于微任务队列,是独立队列)
Promise.then()(真正的微任务) -
特点
process.nextTick 比 Promise.then 更早执行。
没有 UI 渲染阶段(非浏览器)。
setImmediate 与 setTimeout(..., 0) 行为不同。
三、主要区别对比
项目 | 浏览器 | Node.js |
---|---|---|
环境 | 有 UI 渲染 | 无 UI 渲染 |
宏任务示例 | setTimeout, setInterval | setTimeout, setImmediate |
微任务队列 | Promise, MutationObserver | Promise, process.nextTick(优先) |
微任务执行时机 | 每个宏任务后执行所有微任务 | 每个阶段后执行所有微任务(先执行 nextTick) |
特殊队列 | 无 nextTick | process.nextTick 队列优先于微任务 |
底层实现 | 浏览器厂商自研 | libuv 实现多平台 I/O |
总结记忆口诀:浏览器关注 UI,先宏后微;Node 有 Tick,分阶段处理。
宏任务(Macrotasks)
定义:
宏任务是事件循环中调度和执行的主要任务类型,每个宏任务代表一个大的、独立的执行单元。它们通常对应于操作系统或者浏览器层面触发的异步事件,比如计时器、用户交互、网络请求回调等。
特点:
每个宏任务之间,JavaScript 执行上下文是清空的,即宏任务执行时是一个全新的执行上下文。
宏任务执行完后,才会触发微任务队列的清空。
浏览器的 UI 渲染也发生在宏任务完成后。
宏任务队列中的任务是顺序执行的,一个宏任务执行完毕后才会执行下一个。
示例:
setTimeout、setInterval、setImmediate(Node.js)、I/O 回调、UI 渲染等。
微任务(Microtasks)
定义:
微任务是在当前宏任务执行完毕后立即执行的小任务,设计目的是允许开发者将某些任务安排得比下一个宏任务更早执行,从而对事件循环做更细粒度的控制。
特点:
微任务队列会在每个宏任务执行结束后立即清空。
微任务在执行时,JavaScript 执行上下文仍然处于当前宏任务的上下文之后,渲染之前。
微任务可以产生新的微任务,因此执行微任务时会持续清空微任务队列,直到队列为空。
微任务优先级高于宏任务。
示例:
Promise.then / catch / finally 回调、MutationObserver、queueMicrotask、Node.js 的 process.nextTick(特殊微任务,优先级最高)。
汇总表格
类型 | 具体任务 | 特点简介 |
---|---|---|
宏任务 | - setTimeout | 任务队列较大,调度周期较长。宏任务会在当前执行栈清空后执行。 |
- setInterval | 定时重复执行的宏任务。 | |
- setImmediate (Node.js) | 类似于setTimeout(fn, 0) ,但优先级更高(Node环境)。 | |
- I/O操作回调 | 网络请求、文件读取等异步操作的回调。 | |
- UI渲染 | 浏览器的渲染工作,宏任务结束后浏览器会进行UI更新。 | |
- messageChannel 消息事件 | 使用MessageChannel API传递的宏任务。 | |
微任务 | - Promise.then | 微任务会在当前宏任务执行完毕后、渲染之前执行,优先级高于宏任务。 |
- Promise.catch | 同then ,是Promise的错误处理微任务。 | |
- Promise.finally | Promise的最终处理微任务。 | |
- MutationObserver | DOM变动监听,微任务队列中执行。 | |
- queueMicrotask | 直接创建微任务,优先级同Promise微任务。 | |
- process.nextTick (Node.js) | 特殊微任务,优先级最高,会在其他微任务之前执行,用于立即执行回调。 |
事件循环(Event Loop)中的执行流程简述
- 执行一个宏任务,比如执行一个 setTimeout 回调或者页面的主代码。
- 执行所有微任务,直到微任务队列为空。
- 进行一次 UI 渲染(如果环境支持,比如浏览器)。
- 开始下一个宏任务,重复上述流程。
示例代码
setTimeout(() => {
console.log('setTimeout');
}, 0);
setImmediate(() => {
console.log('setImmediate');
});
Promise.resolve().then(() => {
console.log('Promise');
});
process.nextTick(() => {
console.log('nextTick');
});
输出顺序可能是:
nextTick
Promise
setTimeout
setImmediate
理论意义总结
- 宏任务代表“较重”的异步操作单位,是事件循环调度的核心。
- 微任务是微调机制,允许任务在当前宏任务结束后立即执行,确保异步任务可以尽快处理,避免事件循环切换过于频繁带来的性能问题。
- 这种划分使得 JavaScript 能保持单线程顺序执行的同时,灵活高效地处理异步事件。