JavaScript事件循环机制:宏任务与微任务的执行艺术

105 阅读3分钟

深入解析JavaScript单线程模型下的事件循环机制,掌握宏任务和微任务的执行顺序

引言:理解JavaScript的单线程本质

JavaScript作为一门单线程语言,意味着它一次只能执行一个任务。这种设计简化了编程模型,但也带来了挑战:如何处理耗时操作(如网络请求、定时器)而不阻塞主线程?事件循环(Event Loop)  就是解决这一问题的核心机制!

本文将带你深入探索事件循环的工作原理,特别是宏任务和微任务的区别与执行顺序,并通过多个实际示例加深理解。

核心概念解析

1. 事件循环的基本流程

  • 执行一个宏任务(如script脚本)
  • 执行所有微任务
  • 进行页面渲染(如果有必要)
  • 开始下一个宏任务

2. 宏任务与微任务的区别

宏任务微任务
setTimeoutPromise.then
setIntervalqueueMicrotask
I/O操作MutationObserver
UI渲染process.nextTick(Node)
script整体代码

实战示例分析

示例1:基础执行顺序

<script>
console.log('script start'); // 同步任务

setTimeout(() => {
  console.log('setTimeout'); // 宏任务
}, 0);

Promise.resolve().then(() => {
  console.log('promise'); // 微任务
});

console.log('script end'); // 同步任务
</script>

执行顺序分析:

  1. 同步任务:script start → script end
  2. 微任务:promise
  3. 宏任务:setTimeout

输出结果:

script start
script end
promise
setTimeout

示例2:MutationObserver微任务

<script>
const target = document.createElement('div');
document.body.appendChild(target);

const observer = new MutationObserver(() => {
  console.log('微任务: MutaionObserver');
});

observer.observe(target, { attributes: true });

// 连续修改DOM
target.setAttribute('data-set', '123');
target.appendChild(document.createTextNode('123'));
target.setAttribute('style', 'background-color: green;');
</script>

关键点:

  • 多个DOM修改会被合并为一次微任务执行
  • MutationObserver回调在渲染前执行,可以获取DOM最新状态

示例3:Node.js中的微任务(3.js)

console.log('Start');

process.nextTick(() => {
  console.log('Process Next Tick'); // Node特有微任务
});

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

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

console.log('end');

Node环境输出:

Start
end
Process Next Tick
Promise Resolved
haha
inner Promise

注意:  Node.js中process.nextTick优先级高于Promise微任务

示例4:批量Promise处理

console.log('同步Start');

const promise1 = Promise.resolve('First Promise');
const promise2 = Promise.resolve('Secend Promise');
const promise3 = new Promise(resolve => {
  console.log('Promise3'); // 同步执行
  resolve('Third Promise');
});

// 批量添加then处理
promise1.then(console.log);
promise2.then(console.log);
promise3.then(console.log);

setTimeout(console.log, 0, '下一把再见');

// 重复添加
promise1.then(console.log);
promise2.then(console.log);
promise3.then(console.log);

console.log('同步end');

执行顺序:

  1. 同步代码
  2. 所有Promise.then微任务(按添加顺序)
  3. setTimeout宏任务

示例5:queueMicrotask API

<script>
console.log('同步');

queueMicrotask(() => {
  console.log('微任务:queueMicrotask');
});

console.log('同步结束');
</script>

现代浏览器支持:  提供标准API直接创建微任务

事件循环流程图解

deepseek_mermaid_20250712_cfa6ab.png

最佳实践与性能优化

  1. 长任务分解:  使用微任务拆分耗时操作

    function processChunk() {
      // 处理数据块
      if (hasMore) {
        queueMicrotask(processChunk);
      }
    }
    queueMicrotask(processChunk);
    
  2. 避免微任务嵌套:  防止微任务无限循环导致页面卡死

  3. 合理使用宏任务:

    // 需要等待UI更新后执行的操作
    function afterRender() {
      requestAnimationFrame(() => {
        setTimeout(doSomethingAfterRender, 0);
      });
    }
    

总结

理解事件循环机制是掌握JavaScript异步编程的关键:

  • 同步任务优先执行
  • 微任务在同步任务之后、渲染之前执行
  • 宏任务在每轮事件循环最后执行
  • 合理利用微任务可以提高应用响应速度
  • 避免在微任务中进行大量计算阻塞渲染