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

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

JavaScript 是单线程语言,通过事件循环实现异步非阻塞,理解宏任务和微任务的执行顺序至关重要。

事件循环模型

JavaScript
┌─────────────────────────────────────────┐
│              事件循环流程                 │
├─────────────────────────────────────────┤
│                                          │
│   ┌──────────┐    ┌──────────────────┐   │
│   │ 调用栈    │ ←→ │ 宿主环境          │   │
│   │ (Call    │    │ (浏览器/Node.js)  │   │
│   │  Stack)  │    └──────────────────┘   │
│   └────┬─────┘            ↓               │
│        │          ┌──────────────┐       │
│        │          │ 宏任务队列     │       │
│        │          │ (Task Queue)  │       │
│        │          └──────────────┘       │
│        ↓                  ↓               │
│   ┌──────────┐    ┌──────────────┐       │
│   │ 微任务队列 │    │ 执行一个宏任务  │       │
│   │(Microtask │    │              │       │
│   │  Queue)  │    │              │       │
│   └──────────┘    └──────────────┘       │
│                                          │
└─────────────────────────────────────────┘

执行顺序

JavaScript
1. 执行同步代码(调用栈)
2. 调用栈清空后,执行所有微任务
3. 执行一个宏任务
4. 重复步骤 2-3

宏任务(Macro Task)

常见宏任务

来源API
定时器setTimeout, setInterval
I/O文件读写、网络请求
UI 渲染requestAnimationFrame
事件用户交互事件
setImmediateNode.js

宏任务示例

JavaScript
console.log('script start');

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

console.log('script end');

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

微任务(Micro Task)

常见微任务

来源API
Promisethen, catch, finally
queueMicrotaskqueueMicrotask()
MutationObserverDOM 变化监听
process.nextTickNode.js(优先级最高)

微任务示例

JavaScript
console.log('script start');

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

console.log('script end');

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

完整执行顺序

JavaScript
console.log('1. script start');

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

Promise.resolve()
  .then(() => console.log('3. promise1'))
  .then(() => console.log('4. promise2'));

console.log('5. script end');

// 执行过程:
// 1. 同步执行:1, 5
// 2. 调用栈清空,执行微任务:3, 4
// 3. 执行宏任务:2

// 输出:
// 1. script start
// 5. script end
// 3. promise1
// 4. promise2
// 2. setTimeout

复杂案例分析

嵌套微任务

JavaScript
console.log('1');

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

Promise.resolve()
  .then(() => {
    console.log('3');
    Promise.resolve()
      .then(() => console.log('4'));
  })
  .then(() => console.log('5'));

console.log('6');

// 输出:1, 6, 3, 4, 5, 2

async/await 执行

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

async/await 等价转换

JavaScript
async function foo() {
  console.log(1);
  await bar();
  console.log(3);
}

function bar() {
  console.log(2);
}

// 等价于
function foo() {
  console.log(1);
  return Promise.resolve(bar()).then(() => {
    console.log(3);
  });
}

requestAnimationFrame

JavaScript
console.log('script start');

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

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

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

console.log('script end');

// 输出(浏览器):
// script start
// script end
// promise
// raf(渲染前执行)
// setTimeout

raf 与事件循环

JavaScript
宏任务 → 微任务 → requestAnimationFrame → 渲染 → 宏任务

Node.js 差异

process.nextTick

JavaScript
// Node.js 特有,优先于 Promise
process.nextTick(() => {
  console.log('nextTick');
});

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

// 输出:nextTick, promise

Node.js 事件循环阶段

text
   ┌───────────────────────┐
┌─>│        timers         │ setTimeout/setInterval
│  └───────────┬───────────┘
│  ┌───────────┴───────────┐
│  │     pending callbacks │ I/O 回调
│  └───────────┬───────────┘
│  ┌───────────┴───────────┐
│  │       idle, prepare   │
│  └───────────┬───────────┘
│  ┌───────────┴───────────┐
│  │         poll         │ 轮询
│  └───────────┬───────────┘
│  ┌───────────┴───────────┐
│  │        check         │ setImmediate
│  └───────────┬───────────┘
│  ┌───────────┴───────────┐
└──┤    close callbacks    │
   └───────────────────────┘

setTimeout vs setImmediate

text
// 在 I/O 回调中
const fs = require('fs');

fs.readFile(__filename, () => {
  setTimeout(() => console.log('timeout'), 0);
  setImmediate(() => console.log('immediate'));
});

// 输出:immediate, timeout(setImmediate 先执行)

// 在主模块中
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
// 输出不确定,取决于 CPU 性能

实践技巧

优先使用微任务

text
// ✅ 使用微任务进行异步操作
Promise.resolve().then(doSomething);

// ❌ 避免不必要的宏任务
setTimeout(doSomething, 0);

批量更新

text
let pending = false;

function queueUpdate() {
  if (!pending) {
    pending = true;
    Promise.resolve().then(() => {
      pending = false;
      // 批量处理更新
      flushUpdates();
    });
  }
}

微任务在每次事件循环之间执行,宏任务在事件循环中执行。合理利用可优化性能。

要点总结

类型示例执行时机
同步代码主线程代码最先
微任务Promise.then同步后,宏任务前
宏任务setTimeout每次循环一个
  • 同步代码优先执行
  • 微任务队列清空后才执行宏任务
  • 宏任务每次只执行一个
  • Node.js 中 nextTick 优先于 Promise
  • async/await 是 Promise 的语法糖

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

← 上一篇 JavaScript this 绑定规则
下一篇 → JavaScript 内存管理与优化
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

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

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