JavaScript 事件循环宏任务与微任务实例详解

 更新时间:2025年11月24日 10:09:46   作者:|晴 天|  
本文解析了JavaScript事件循环机制,重点讲解了宏任务(MacroTasks)和微任务(MicroTasks)的区别、执行顺序及其实际应用,通过理解事件循环,可以编写更高效、可靠的异步代码,优化性能,避免阻塞用户界面,感兴趣的朋友跟随小编一起看看吧

JavaScript 作为一门单线程语言,却能高效处理异步操作,这得益于其精巧的事件循环机制。理解事件循环中的宏任务和微任务是掌握 JavaScript 异步编程的关键。本文将深入解析这两者的区别、执行顺序和实际应用。

为什么需要事件循环?

JavaScript 设计之初主要用于浏览器交互,如果采用多线程会带来复杂的同步问题(如 DOM 操作冲突)。因此,JavaScript 使用单线程 + 事件循环的方案:

  • 单线程:避免复杂的线程同步问题
  • 事件循环:通过任务队列处理异步操作,不阻塞主线程

事件循环的基本原理

事件循环的核心可以简化为以下流程:

1. 执行同步代码(调用栈)

2. 检查微任务队列,执行所有微任务

3. 检查宏任务队列,取出第一个执行

4. 重复步骤1-3

宏任务 vs 微任务

宏任务(Macro Tasks)

宏任务代表离散的、独立的工作单元。常见的宏任务包括:

// 宏任务示例
setTimeout(() => {
  console.log('setTimeout - 宏任务');
}, 0);
setInterval(() => {
  console.log('setInterval - 宏任务');
}, 1000);
// I/O 操作
fs.readFile('file.txt', (err, data) => {
  console.log('I/O 操作 - 宏任务');
});
// UI 渲染(浏览器)
// 页面渲染本身也是一个宏任务

微任务(Micro Tasks)

微任务通常是在当前任务执行结束后立即执行的任务。常见的微任务包括:

// Promise - 微任务
Promise.resolve().then(() => {
  console.log('Promise.then - 微任务');
});
// MutationObserver - 微任务
const observer = new MutationObserver(() => {
  console.log('DOM 变化 - 微任务');
});
// process.nextTick (Node.js) - 微任务
process.nextTick(() => {
  console.log('process.nextTick - 微任务');
});
// queueMicrotask API
queueMicrotask(() => {
  console.log('queueMicrotask - 微任务');
});

执行顺序详解

让我们通过代码示例来理解执行顺序:

console.log('1. 同步代码开始');
setTimeout(() => {
  console.log('6. setTimeout - 宏任务');
}, 0);
Promise.resolve().then(() => {
  console.log('4. Promise 1 - 微任务');
}).then(() => {
  console.log('5. Promise 2 - 微任务');
});
queueMicrotask(() => {
  console.log('3. queueMicrotask - 微任务');
});
console.log('2. 同步代码结束');
// 执行结果:
// 1. 同步代码开始
// 2. 同步代码结束
// 3. queueMicrotask - 微任务
// 4. Promise 1 - 微任务
// 5. Promise 2 - 微任务
// 6. setTimeout - 宏任务

更复杂的例子

console.log('脚本开始');
setTimeout(() => {
  console.log('setTimeout 1');
  Promise.resolve().then(() => {
    console.log('Promise in setTimeout');
  });
}, 0);
Promise.resolve().then(() => {
  console.log('Promise 1');
  return Promise.resolve();
}).then(() => {
  console.log('Promise 2');
  setTimeout(() => {
    console.log('setTimeout in Promise');
  }, 0);
});
console.log('脚本结束');
// 执行结果:
// 脚本开始
// 脚本结束
// Promise 1
// Promise 2
// setTimeout 1
// Promise in setTimeout
// setTimeout in Promise

事件循环的完整流程

更详细的事件循环流程如下:

1. 执行同步代码直到调用栈清空

2. 执行所有微任务直到微任务队列清空

3. 如有需要,执行 UI 渲染(浏览器)

4. 从宏任务队列中取出一个任务执行

5. 回到步骤1,开始新一轮循环

可视化流程

[同步代码] → [微任务队列] → [渲染] → [宏任务队列] 
     ↓           ↓           ↓         ↓
   执行        全部执行     可能执行    取一个执行

实际应用场景

1. 优化性能 - 批量 DOM 更新

// 不好的做法:可能导致多次重排
function updateMultipleElements() {
  element1.style.color = 'red';
  element2.style.color = 'blue';
  element3.style.color = 'green';
}
// 好的做法:使用微任务批量更新
function updateMultipleElementsOptimized() {
  queueMicrotask(() => {
    element1.style.color = 'red';
    element2.style.color = 'blue';
    element3.style.color = 'green';
  });
}

2. 确保代码在当前任务结束后执行

// 在状态更新后执行某些操作
let state = { count: 0 };
function updateState(newCount) {
  state.count = newCount;
  // 使用微任务确保在状态更新后执行
  Promise.resolve().then(() => {
    console.log('状态已更新:', state.count);
    updateUI();
  });
}

3. 处理用户输入

// 确保在处理用户输入前完成所有微任务
button.addEventListener('click', () => {
  // 同步代码
  console.log('按钮被点击');
  // 微任务 - 在渲染前执行
  Promise.resolve().then(() => {
    console.log('处理点击的微任务');
  });
  // 宏任务 - 在渲染后执行
  setTimeout(() => {
    console.log('处理点击的宏任务');
  }, 0);
});

常见陷阱与最佳实践

1. 避免微任务无限循环

// 错误示例:会导致微任务无限循环
function infiniteMicrotask() {
  Promise.resolve().then(() => {
    console.log('微任务执行');
    infiniteMicrotask(); // 递归调用
  });
}
// 正确做法:使用宏任务打破循环
function safeRecursiveTask() {
  Promise.resolve().then(() => {
    console.log('微任务执行');
    // 使用 setTimeout 让出控制权
    setTimeout(safeRecursiveTask, 0);
  });
}

2. 理解不同环境的差异

// Node.js 与浏览器的微任务优先级差异
console.log('start');
Promise.resolve().then(() => {
  console.log('Promise');
});
process.nextTick(() => {
  console.log('nextTick');
});
console.log('end');
// Node.js 输出:
// start
// end
// nextTick
// Promise
// 浏览器输出(无 process.nextTick):
// start
// end
// Promise

3. 合理选择任务类型

// 根据场景选择任务类型:
// 紧急的、需要立即执行的任务 - 使用微任务
function urgentTask() {
  queueMicrotask(() => {
    // 立即执行但不在当前调用栈
  });
}
// 不紧急的、可以延迟的任务 - 使用宏任务
function lazyTask() {
  setTimeout(() => {
    // 可以等待的任务
  }, 0);
}
// 需要让浏览器渲染的任务 - 使用宏任务
function renderTask() {
  setTimeout(() => {
    // 确保 UI 有机会更新
  }, 0);
}

调试技巧

1. 使用 console 跟踪执行顺序

console.log('同步 1');
setTimeout(() => console.log('宏任务 1'), 0);
Promise.resolve()
  .then(() => console.log('微任务 1'))
  .then(() => console.log('微任务 2'));
console.log('同步 2');

2. 使用 Performance API 分析

// 标记任务执行时间
performance.mark('task-start');
// 执行一些操作
doSomeWork();
performance.mark('task-end');
performance.measure('task-duration', 'task-start', 'task-end');

总结

理解 JavaScript 事件循环中的宏任务和微任务对于编写高效、可靠的异步代码至关重要:

  • 微任务在当前任务结束后立即执行,优先级高于宏任务
  • 宏任务在每次事件循环中执行一个,让浏览器有机会进行渲染
  • 合理的任务调度可以优化性能,避免阻塞用户界面
  • 掌握执行顺序有助于调试复杂的异步代码流程

通过合理运用宏任务和微任务,你可以更好地控制代码的执行时机,创建更流畅的用户体验。记住这个简单的规则:同步代码 → 所有微任务 → 一个宏任务 → 重复

到此这篇关于JavaScript 事件循环宏任务与微任务详解的文章就介绍到这了,更多相关js宏任务与微任务内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • JS实现canvas仿ps橡皮擦刮卡效果详解

    JS实现canvas仿ps橡皮擦刮卡效果详解

    这篇文章主要为大家详细介绍了使用js中的Canvas实现橡皮擦效果,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-11-11
  • 在Web关闭页面时发送Ajax请求的实现方法

    在Web关闭页面时发送Ajax请求的实现方法

    这篇文章主要给大家介绍了关于如何在Web关闭页面时发送Ajax请求的实现方法,文中通过示例代码以及图文介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-03-03
  • JavaScript去掉数组中的重复元素

    JavaScript去掉数组中的重复元素

    在写程序过程中,经常会遇到去除数组中重复元素的需求。要实现这个功能其实并不难
    2011-01-01
  • JavaScript获取Excel表格的列序号和列名

    JavaScript获取Excel表格的列序号和列名

    这篇文章主要介绍了JavaScript获取Excel表格的列序号和列名,在处理Excel文件时,通常要获取xx列的数据,这就要求先找到列序号,下文关于列名获取需要的小伙伴可以参考一下
    2022-05-05
  • 基于JS实现小区楼的电梯运行程序

    基于JS实现小区楼的电梯运行程序

    本文介绍了如何使用JavaScript实现一个简单的小区楼电梯运行程序,用户可以通过点击楼层来选择目标楼层,并模拟电梯的上下行,文章还提示了如何扩展该程序,例如添加更多楼层、优化移动逻辑和添加动画效果,感兴趣的朋友一起看看吧
    2025-01-01
  • web-view内嵌H5与uniapp数据的实时传递解决方案

    web-view内嵌H5与uniapp数据的实时传递解决方案

    这篇文章主要介绍了web-view内嵌H5与uniapp数据的实时传递,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-07-07
  • JS中作用域以及变量范围分析

    JS中作用域以及变量范围分析

    这篇文章主要介绍了JS中作用域以及变量范围分析,需要的朋友可以参考下
    2020-07-07
  • web前端开发也需要日志

    web前端开发也需要日志

    web前端开发过程中调试是一个不可避免的过程,我们有众多的浏览器可供选择,但是如果您要调试的平台浏览器不是那么先进呢
    2010-12-12
  • JS返回只包含数字类型的数组实例分析

    JS返回只包含数字类型的数组实例分析

    这篇文章主要介绍了JS返回只包含数字类型的数组实现方法,结合实例形式分析了循环遍历数组及正则匹配两种实现技巧,需要的朋友可以参考下
    2016-12-12
  • 微信小程序实现九宫格效果

    微信小程序实现九宫格效果

    这篇文章主要为大家详细介绍了微信小程序实现九宫格效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-07-07

最新评论