全部学科
Python全栈
python
NodeJS全栈
nodejs
小程序首页
📅 2026-05-16 8 分钟 ✍️ juanwangdev

JavaScript 事件循环与微任务/宏任务

事件循环(Event Loop)是 JavaScript 异步执行的调度机制,理解微任务与宏任务的执行顺序是掌握异步的关键。

事件循环模型

JavaScript
// JavaScript 运行时结构
// ┌───────────────────────────┐
// │      Call Stack(调用栈)    │
// │   当前执行的函数             │
// └───────────────────────────┘
//            ↓
// ┌───────────────────────────┐
// │     Microtask Queue        │ ← 微任务(Promise.then 等)
// │   执行完栈后立即清空         │
// └───────────────────────────┘
//            ↓
// ┌───────────────────────────┐
// │     Macrotask Queue        │ ← 宏任务(setTimeout 等)
// │   每次循环取一个执行         │
// └───────────────────────────┘
//            ↓
// ┌───────────────────────────┐
// │      Rendering(渲染)       │ ← UI 更新(可选)
// └───────────────────────────┘

执行顺序规则

JavaScript
// 事件循环执行流程:
// 1. 执行调用栈中的同步代码
// 2. 调用栈清空后,执行所有微任务
// 3. 执行一个宏任务
// 4. 渲染更新(如果有)
// 5. 回到步骤 2

console.log('1. 同步开始');

setTimeout(() => console.log('2. 宏任务 setTimeout'), 0);

Promise.resolve()
    .then(() => console.log('3. 微任务 Promise.then 1'))
    .then(() => console.log('4. 微任务 Promise.then 2'));

console.log('5. 同步结束');

// 输出顺序:
// 1. 同步开始
// 5. 同步结束
// 3. 微任务 Promise.then 1
// 4. 微任务 Promise.then 2
// 2. 宏任务 setTimeout

宏任务 Macrotask

JavaScript
// 宏任务来源:
// - script(整体代码)
// - setTimeout / setInterval
// - setImmediate(Node.js)
// - I/O 操作
// - UI 渲染
// - requestAnimationFrame(浏览器,渲染前)

// setTimeout 延迟最小 4ms
setTimeout(() => console.log('timeout'), 0);  // 实际至少 4ms

// setImmediate(Node.js)
setImmediate(() => console.log('immediate'));

// requestAnimationFrame(渲染帧前执行)
requestAnimationFrame(() => console.log('animation frame'));

微任务 Microtask

JavaScript
// 微任务来源:
// - Promise.then / catch / finally
// - queueMicrotask()
// - MutationObserver(DOM 变化监听)
// - process.nextTick(Node.js,优先级最高)

// Promise 创建微任务
Promise.resolve().then(() => console.log('微任务'));

// queueMicrotask 手动添加微任务
queueMicrotask(() => console.log('手动微任务'));

// MutationObserver
const observer = new MutationObserver(() => {
    console.log('DOM 变化微任务');
});
observer.observe(document.body, { attributes: true });
document.body.setAttribute('data-test', '1');  // 触发微任务

微任务优先级高于宏任务

JavaScript
console.log('script start');

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

Promise.resolve().then(() => {
    console.log('promise1');
}).then(() => {
    console.log('promise2');
});

console.log('script end');

// 输出:
// script start
// script end
// promise1
// promise2
// setTimeout

// 解释:
// 1. 同步代码执行:script start → script end
// 2. 调用栈清空,执行所有微任务:promise1 → promise2
// 3. 执行一个宏任务:setTimeout

微任务队列清空机制

JavaScript
// 每次宏任务前,微任务队列必须清空
async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');  // 微任务
}

async function async2() {
    console.log('async2');
}

console.log('script start');

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

async1();

new Promise(resolve => {
    console.log('promise1');
    resolve();
}).then(() => console.log('promise2'));

console.log('script end');

// 输出:
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeout

// 解释:
// await 后的代码相当于 Promise.then,加入微任务队列
// 微任务队列全部执行完后才执行 setTimeout

Node.js 事件循环差异

JavaScript
// Node.js 事件循环阶段:
//   timers(setTimeout/setInterval)
//   pending callbacks
//   idle, prepare
//   poll(I/O)
//   check(setImmediate)
//   close callbacks

// process.nextTick 优先级高于 Promise
process.nextTick(() => console.log('nextTick'));
Promise.resolve().then(() => console.log('promise'));
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));

// Node.js 输出:
// nextTick → promise → timeout/immediate(取决于执行时机)

// setTimeout vs setImmediate
// 在 I/O 回调中:immediate 先于 timeout
const fs = require('fs');
fs.readFile('file', () => {
    setTimeout(() => console.log('timeout'), 0);
    setImmediate(() => console.log('immediate'));
});
// 输出:immediate → timeout(在 poll 阶段后 check 先于 timers)

requestAnimationFrame 时机

JavaScript
// requestAnimationFrame 在渲染前执行
// 不是宏任务也不是微任务,在微任务后、渲染前

console.log('sync');

setTimeout(() => console.log('macro'), 0);

Promise.resolve().then(() => console.log('micro'));

requestAnimationFrame(() => console.log('raf'));

// 输出顺序(浏览器):
// sync → micro → raf → macro
// 微任务 → requestAnimationFrame → 宏任务

// raf 内的微任务在渲染前执行
requestAnimationFrame(() => {
    Promise.resolve().then(() => console.log('raf micro'));
    console.log('raf sync');
});
// raf sync → raf micro → 渲染

常见面试题解析

JavaScript
// 经典题目
console.log('1');

setTimeout(() => {
    console.log('2');
    Promise.resolve().then(() => console.log('3'));
}, 0);

Promise.resolve()
    .then(() => console.log('4'))
    .then(() => {
        console.log('5');
        setTimeout(() => console.log('6'), 0);
    });

console.log('7');

// 输出:1 → 7 → 4 → 5 → 2 → 3 → 6

// 解析:
// 第一轮循环:
//   同步:1, 7
//   微任务:4(then1 执行,then2 加入)
// 第二轮循环(微任务继续):
//   微任务:5(then2 执行,setTimeout(6) 加入宏任务)
//   微任务清空
// 第三轮循环:
//   宏任务:setTimeout(2)(then(3) 加入微任务)
//   微任务:3
//   微任务清空
// 第四轮循环:
//   宏任务:setTimeout(6)

注意事项

  • 微任务在调用栈清空后立即执行,宏任务等待事件循环
  • Promise 回调是微任务,setTimeout 是宏任务
  • await 后代码等同于 Promise.then,也是微任务
  • Node.js process.nextTick 优先级最高,高于 Promise
JavaScript
// 微任务可能阻塞事件循环
function infiniteMicrotasks() {
    Promise.resolve().then(() => {
        console.log('微任务');
        infiniteMicrotasks();  // 无限添加微任务
    });
}
// 宏任务永远无法执行,页面卡死

// 正确做法:定期让出控制权
function batchProcess(items) {
    const batch = items.splice(0, 100);
    batch.forEach(process);

    if (items.length) {
        // 使用宏任务让出控制权
        setTimeout(() => batchProcess(items), 0);
    }
}

要点总结

  • 事件循环:同步代码 → 清空微任务 → 一个宏任务 → 循环
  • 微任务:Promise.then/catch/finally、queueMicrotask、MutationObserver
  • 宏任务:setTimeout/setInterval、setImmediate、I/O、script
  • 微任务优先级高于宏任务,每次宏任务前微任务队列必须清空
  • await 后代码等同于微任务
  • Node.js process.nextTick 优先级最高
  • requestAnimationFrame 在微任务后、渲染前执行
  • 避免无限微任务阻塞事件循环

📝 发现内容有误?点击此处直接编辑

← 上一篇 JavaScript Web Workers 与多线程
下一篇 → JavaScript 并发控制(Promise.all、Promise.race、Promise.allSettled)
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库