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代码至关重要,特别是在处理复杂的异步操作和性能优化时。

相关推荐
仅此,9 分钟前
前端接收了id字段,发送给后端就变了
java·前端·javascript·spring·typescript
Lovely Ruby11 分钟前
[前端] 封装一下 echart 6,发布到 npm
前端·npm·node.js
BD_Marathon13 分钟前
NPM_常见命令
前端·npm·node.js
绿鸳16 分钟前
12.17面试题
前端
Huanzhi_Lin29 分钟前
禁用谷歌/google/chrome浏览器更新
前端·chrome
咸鱼加辣33 分钟前
【前端的crud】DOM 就是前端里的“数据库”
前端·数据库
kong790692837 分钟前
环境搭建-运行前端工程(Nginx)
前端·nginx·前端工程
成都证图科技有限公司1 小时前
Bus Hound概述
前端
PythonFun1 小时前
WPS中表格行高无法手动调整怎么办?
前端·html·wps
IT_陈寒1 小时前
JavaScript性能优化:7个V8引擎内部原理帮你减少90%内存泄漏的实战技巧
前端·人工智能·后端