深入理解JavaScript事件循环:从宏任务到微任务

举报
yd_210610071 发表于 2026/10/24 21:50:45 2026/10/24
【摘要】 深入理解JavaScript事件循环:从宏任务到微任务本文深入剖析JavaScript事件循环机制,帮助你理解异步编程的核心原理,掌握宏任务与微任务的执行顺序。 为什么需要事件循环?JavaScript是单线程语言,但能处理大量并发请求,这背后全靠事件循环机制。理解事件循环,是写出高质量异步代码的基础。 执行栈与调用栈JavaScript引擎使用调用栈(Call Stack)来跟踪函数执行...

深入理解JavaScript事件循环:从宏任务到微任务

本文深入剖析JavaScript事件循环机制,帮助你理解异步编程的核心原理,掌握宏任务与微任务的执行顺序。

为什么需要事件循环?

JavaScript是单线程语言,但能处理大量并发请求,这背后全靠事件循环机制。理解事件循环,是写出高质量异步代码的基础。

执行栈与调用栈

JavaScript引擎使用调用栈(Call Stack)来跟踪函数执行:

function multiply(a, b) {
  return a * b;
}

function square(n) {
  return multiply(n, n);
}

console.log(square(5));
// 执行顺序:square(5) -> multiply(5, 5) -> console.log(25)

调用栈遵循"后进先出"原则,函数调用会压栈,执行完毕会出栈。

浏览器环境的事件循环

宏任务(MacroTask)与微任务(MicroTask)

事件循环将任务分为两类:

类型 示例
宏任务 setTimeout、setInterval、setImmediate(Node.js)、I/O、UI渲染
微任务 Promise.then/catch/finally、MutationObserver、queueMicrotask、process.nextTick(Node.js)

执行顺序

1. 执行一个宏任务(如整体代码)
2. 执行过程中产生的所有微任务,全部清空
3. 渲染(浏览器)
4. 取出下一个宏任务
5. 重复2-4步

实战代码分析

console.log('start');

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

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

console.log('end');

// 输出顺序:
// start -> end -> promise1 -> promise2 -> setTimeout

分析过程:

  1. 同步代码执行:start、end
  2. setTimeout回调放入宏任务队列
  3. 两个Promise回调放入微任务队列
  4. 清空微任务队列:promise1、promise2
  5. 执行下一个宏任务:setTimeout

Node.js环境的事件循环

Node.js的事件循环与浏览器略有不同,分为6个阶段:

┌───────────────────────┐
│        timers         │ ← 执行setTimeout/setInterval回调
└───────────────────────┘
          ↓
┌───────────────────────┐
│    pending callbacks  │ ← 执行I/O回调(某些系统级操作)
└───────────────────────┘
          ↓
┌───────────────────────┐
│       idle/prepare    │ ← 内部使用
└───────────────────────┘
          ↓
┌───────────────────────┐
│        poll           │ ← 获取新的I/O事件,执行I/O回调
└───────────────────────┘
          ↓
┌───────────────────────┐
│        check          │ ← 执行setImmediate回调
└───────────────────────┘
          ↓
┌───────────────────────┐
│   closing callbacks   │ ← 执行关闭事件回调(如socket.on('close'))
└───────────────────────┘

process.nextTick的特殊性

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

process.nextTick(() => {
  console.log('nextTick');
});

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

// 输出:nextTick -> promise -> timeout

process.nextTick优先级高于Promise微任务,它在当前操作完成后、事件循环继续前执行。

常见陷阱与最佳实践

陷阱1:阻塞事件循环

// ❌ 错误:阻塞事件循环
function heavyComputation() {
  let result = 0;
  for (let i = 0; i < 1000000000; i++) {
    result += i;
  }
  return result;
}

// ✅ 正确:使用Web Worker或拆分任务
async function processInChunks(data, chunkSize = 1000) {
  for (let i = 0; i < data.length; i += chunkSize) {
    await processChunk(data.slice(i, i + chunkSize));
    await new Promise(resolve => setTimeout(resolve, 0));
  }
}

陷阱2:微任务饥饿

// ❌ 错误:无限递归微任务,阻塞事件循环
function microtaskRecursion() {
  Promise.resolve().then(() => {
    console.log('microtask');
    microtaskRecursion();
  });
}

// ✅ 正确:限制微任务数量或使用宏任务

最佳实践

  1. 避免在微任务中执行耗时操作
  2. 合理使用queueMicrotask
  3. 理解process.nextTick与setImmediate的区别
  4. 使用async/await简化异步代码

实战:手写事件循环模拟

class EventLoop {
  constructor() {
    this.macrotaskQueue = [];
    this.microtaskQueue = [];
  }

  // 添加宏任务
  setTimeout(callback) {
    this.macrotaskQueue.push(callback);
  }

  // 添加微任务
  queueMicrotask(callback) {
    this.microtaskQueue.push(callback);
  }

  // 执行事件循环
  run() {
    while (this.macrotaskQueue.length > 0 || this.microtaskQueue.length > 0) {
      // 执行一个宏任务
      if (this.macrotaskQueue.length > 0) {
        const macroTask = this.macrotaskQueue.shift();
        macroTask();
      }

      // 清空所有微任务
      while (this.microtaskQueue.length > 0) {
        const microTask = this.microtaskQueue.shift();
        microTask();
      }
    }
  }
}

// 使用示例
const loop = new EventLoop();

loop.setTimeout(() => console.log('macro1'));
loop.queueMicrotask(() => console.log('micro1'));
loop.queueMicrotask(() => console.log('micro2'));

loop.run();
// 输出:micro1 -> micro2 -> macro1

总结

概念 说明
宏任务 setTimeout、setInterval、I/O、UI渲染
微任务 Promise、MutationObserver、queueMicrotask
执行顺序 宏任务 -> 清空微任务 -> 渲染 -> 下一个宏任务
Node.js 6个阶段,process.nextTick优先级最高

掌握事件循环,是理解JavaScript异步编程的关键。在实际开发中,合理利用宏任务和微任务,可以写出更高效、更可维护的代码。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。