JavaScript事件循环总结

JavaScript事件循环总结

概述

JavaScript事件循环是JavaScript运行时处理异步操作的核心机制。它允许JavaScript在执行同步代码的同时,处理异步任务(如定时器、网络请求、用户交互等),而不会阻塞主线程。

核心概念

1. 调用栈 (Call Stack)

  • 用于跟踪当前正在执行的函数
  • 后进先出(LIFO)的数据结构
  • 同步代码在此执行

2. 任务队列 (Task Queue)

  • 宏任务队列:setTimeout、setInterval、I/O操作、UI渲染等
  • 微任务队列:Promise、MutationObserver、process.nextTick(Node.js)

3. 事件循环流程

javascript 复制代码
// 事件循环的简化表示
while (eventLoop.waitForTask()) {
  // 1. 执行一个宏任务
  const task = eventLoop.getNextMacroTask();
  execute(task);
  
  // 2. 执行所有微任务
  while (eventLoop.hasMicrotasks()) {
    const microtask = eventLoop.getNextMicrotask();
    execute(microtask);
  }
  
  // 3. 如有需要,进行UI渲染
  if (shouldRender()) {
    render();
  }
}

执行顺序

基本执行流程

  1. 执行同步代码(调用栈)
  2. 执行当前所有微任务
  3. 执行一个宏任务
  4. 重复步骤2-3
javascript 复制代码
console.log('1. 同步代码开始');

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

Promise.resolve()
  .then(() => {
    console.log('4. 微任务 - Promise 1');
    return Promise.resolve();
  })
  .then(() => {
    console.log('5. 微任务 - Promise 2');
  });

console.log('2. 同步代码继续');

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

console.log('7. 同步代码结束');

// 输出顺序:
// 1. 同步代码开始
// 2. 同步代码继续
// 7. 同步代码结束
// 4. 微任务 - Promise 1
// 3. 微任务 - queueMicrotask
// 5. 微任务 - Promise 2
// 6. 宏任务 - setTimeout

任务分类

宏任务 (Macrotasks)

  • setTimeout / setInterval
  • setImmediate (Node.js)
  • I/O 操作
  • UI 渲染
  • 事件回调
  • requestAnimationFrame

微任务 (Microtasks)

  • Promise.then / catch / finally
  • queueMicrotask
  • MutationObserver
  • process.nextTick (Node.js)

实际示例分析

javascript 复制代码
console.log('脚本开始');

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

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

Promise.resolve()
  .then(() => {
    console.log('Promise 1');
    return Promise.resolve();
  })
  .then(() => {
    console.log('Promise 2');
  });

console.log('脚本结束');

// 执行顺序:
// 1. "脚本开始"
// 2. "脚本结束"
// 3. "Promise 1"
// 4. "Promise 2"
// 5. "setTimeout 1"
// 6. "Promise in setTimeout"
// 7. "setTimeout 2"

Node.js与浏览器差异

Node.js事件循环阶段

  1. timers: 执行setTimeout和setInterval回调
  2. pending callbacks: 执行延迟到下一个循环迭代的I/O回调
  3. idle, prepare: 仅系统内部使用
  4. poll: 检索新的I/O事件,执行I/O相关回调
  5. check: 执行setImmediate回调
  6. close callbacks: 执行关闭事件的回调

浏览器特有

  • requestAnimationFrame
  • 与渲染周期相关的任务

最佳实践

1. 避免阻塞事件循环

javascript 复制代码
// 不良实践 - 同步阻塞操作
function processLargeData(data) {
  // 长时间运行的同步操作
  for (let i = 0; i < 1000000000; i++) {
    // 密集计算
  }
}

// 良好实践 - 使用异步或分块处理
async function processLargeDataAsync(data) {
  // 使用Web Workers或分块处理
  return await processInChunks(data);
}

2. 合理使用微任务

javascript 复制代码
// 使用微任务确保在UI更新前执行
function updateState(newState) {
  Promise.resolve().then(() => {
    // 在下一个微任务中更新状态
    state = newState;
    updateUI();
  });
}

3. 避免微任务嵌套过深

javascript 复制代码
// 避免这种情况
Promise.resolve().then(() => {
  Promise.resolve().then(() => {
    Promise.resolve().then(() => {
      // 深度嵌套
    });
  });
});

调试技巧

1. 使用console.log跟踪执行顺序

javascript 复制代码
console.log('同步代码');
setTimeout(() => console.log('宏任务'), 0);
Promise.resolve().then(() => console.log('微任务'));

2. 使用Performance API分析

javascript 复制代码
// 测量代码执行时间
performance.mark('start');
// 执行代码
performance.mark('end');
performance.measure('执行时间', 'start', 'end');

总结

JavaScript事件循环机制确保了:

  • 单线程的非阻塞执行
  • 异步任务的有序处理
  • 用户界面的响应性

理解事件循环对于编写高效、无阻塞的JavaScript代码至关重要,特别是在处理复杂的异步操作和性能优化时。

相关推荐
j***63081 小时前
SpringbootActuator未授权访问漏洞
android·前端·后端
forestsea1 小时前
现代 JavaScript 加密技术详解:Web Crypto API 与常见算法实践
前端·javascript·算法
_前端小李_1 小时前
pnpm老是默认把包安装在C盘很头疼?教你快速配置pnpm的全局目录
前端
Cache技术分享1 小时前
254. Java 集合 - 使用 Lambda 表达式操作 Map 的值
前端·后端
CryptoPP1 小时前
使用 KLineChart 这个轻量级的前端图表库
服务器·开发语言·前端·windows·后端·golang
p***43482 小时前
前端路由管理
前端
是一碗螺丝粉2 小时前
React Native 运行时深度解析
前端·react native·react.js
Jing_Rainbow2 小时前
【前端三剑客-9 /Lesson17(2025-11-01)】CSS 盒子模型详解:从标准盒模型到怪异(IE)盒模型📦
前端·css·前端框架