宏任务微任务的定义
宏任务
宏任务通常指那些执行时间较长,或者可以独立于其他任务的任务。常见的宏任务包括:
setTimeoutsetIntervalsetImmediate(Node.js 环境)I/O
1.setTimeout
- 在指定的延迟时间后执行一次回调函数。
- 适用于 单次延迟任务(如延迟加载、防抖/节流)。
setTimeout(() => {
console.log("2秒后执行");
}, 2000);
2.setInterval
- 每隔指定的时间重复执行回调函数。
- 适用于 周期性任务(如轮询、动画)。
let count = 0;
const timer = setInterval(() => {
console.log(`第 ${++count} 次执行`);
if (count >= 5) clearInterval(timer); // 停止
}, 1000);
3.setImmediate(Node.js特有)
- 在当前事件循环的检查阶段(check)执行回调,执行一次。
- 适用于 希望代码尽快执行 的场景。
4.I/O(输入输出)
- 异步非阻塞特性:I/O 操作(如文件读写、网络请求)需要等待系统底层完成,完成后回调被推入宏任务队列。
- 与同步代码分离:I/O 回调不会立即执行,而是等待事件循环轮到I/O宏任务队列时处理。
Node.js事件循环阶段:
┌───────────────────────┐
│ Timers │ ◀─ `setTimeout`/`setInterval`
└──────────┬────────────┘
│
┌──────────▼────────────┐
│ I/O Callbacks │ ◀─ **已完成 I/O 的回调**(如 `fs.readFile`、网络请求)
└──────────┬────────────┘
│
┌──────────▼────────────┐
│ Idle/Prepare │ ◀─ 内部使用
└──────────┬────────────┘
│
┌──────────▼────────────┐
│ Poll │ ◀─ 检索新的 I/O 事件(如新连接、文件读取)
└──────────┬────────────┘
│
┌──────────▼────────────┐
│ Check │ ◀─ `setImmediate` 回调
└──────────┬────────────┘
│
┌──────────▼────────────┐
│ Close Callbacks │ ◀─ 关闭事件(如 `socket.on('close')`)
└───────────────────────┘
例子:
const fs = require("fs");
console.log("同步代码 1");
fs.readFile("file.txt", (err, data) => {
console.log("I/O 回调执行"); // 宏任务
});
setTimeout(() => console.log("setTimeout"), 0);
setImmediate(() => console.log("setImmediate"));
console.log("同步代码 2");
/**
* 输出顺序:
* 同步代码 1
* 同步代码 2
* setTimeout (Timers Phase)
* I/O 回调执行 (I/O Callbacks Phase)
* setImmediate (Check Phase)
*/
微任务
微任务通常指那些执行时间短,更细粒度的任务,比宏任务优先级高。常见的微任务包括:
Promise.then/catch/finallyMutationObserverqueueMicrotask(浏览器、Node.js环境)process.nextTick(Node.js 环境)
Promise.then/catch/finally
- 在 Promise 中,只有
then、catch、finally的回调会被放入微任务队列,而其他 Promise 方法(如Promise.resolve()、Promise.reject()、Promise.all()、Promise.race()等)本身不会直接产生微任务,它们只是用于构造或处理 Promise 对象。 Promise构造函数里的resolve/reject/all/race/allSettled/any是同步的
new Promise((resolve) => {
console.log("同步执行"); // 同步代码
resolve("微任务触发");
}).then((val) => console.log(val)); // 微任务
new Promise(fn)的fn是 同步执行 的。resolve()或reject()它们只是 同步 返回一个 Promise 对象,不会直接产生微任务,只有resolve()或reject()后,then/catch/finally才会安排微任务。
MutationObserver
MutationObserver是浏览器提供的一个 异步监听 DOM 变化 的 API。
const observer = new MutationObserver((mutations) => {
console.log("DOM 发生变化!", mutations);
});
observer.observe(document.body, {
childList: true, // 监听子节点变化
attributes: true, // 监听属性变化
subtree: true // 监听所有后代节点
});
// 触发 DOM 变化
document.body.appendChild(document.createElement("div"));
MutationObserver的回调是微任务,不会立即执行,而是进入微任务队列。- 即使多次修改 DOM,回调只会执行一次(合并变化)。
MutationObserver和Promise都是微任务,按注册顺序执行。
// 短时间内多次修改 DOM
document.body.appendChild(document.createElement("div"));
document.body.appendChild(document.createElement("p"));
document.body.setAttribute("data-test", "123");
// MutationObserver 回调只会执行一次,包含所有变更记录
queueMicrotask
queueMicrotask 是一个用于将回调函数加入 微任务队列(Microtask Queue) 的全局方法,在 浏览器 和 Node.js 环境中均可使用。
作用
- 它接收一个回调函数,并将其添加到 当前调用栈执行完毕后的微任务队列 中。
- 类似于
Promise.resolve().then(callback),但更直接,不需要创建 Promise 实例。 - 执行时机和 Promise 微任务相同,但比
process.nextTick优先级低(在 Node.js 中)。
console.log("同步任务 1");
queueMicrotask(() => {
console.log("微任务 1");
});
process.nextTick(() => {
console.log("nextTick 微任务");
});
console.log("同步任务 2");
// 输出顺序:
// 同步任务 1
// 同步任务 2
// nextTick 微任务 (nextTick 优先级更高)
// 微任务 1
process.nextTick
process.nextTick 在 Node.js 中被视为一种微任务(Microtask) ,但它不属于标准的 Promise 微任务队列,而是属于 Next Tick Queue,具有更高的优先级。
一段容易错的题
fs.readFile("file.txt", () => {
console.log("I/O 回调(宏任务)");
Promise.resolve().then(() => {
console.log("微任务"); // 在 I/O 回调后立即执行
});
});
//I/O 回调(宏任务)
//微任务
我在第一反应是先输出微任务O.O,理由是先执行微任务在执行宏任务,大错特错!
注意:
微任务不会抢占当前正在执行的宏任务,而是在 当前宏任务完成后 执行。
微任务是在当前宏任务执行完后、下一个宏任务开始前执行的 。
所以不管是什么宏任务里面套了微任务,都要注意这一点。