JS事件循环

  1. 简要介绍:什么是事件循环?为什么需要事件循环?

  2. 核心概念:调用栈、任务队列(宏任务和微任务)、渲染步骤。

  3. 详细流程:描述事件循环的循环过程,包括如何执行宏任务和微任务。

  4. 举例说明:通过代码示例展示不同任务的执行顺序。

  5. 扩展话题:Node.js事件循环与浏览器事件循环的区别,requestAnimationFrame,requestIdleCallback等。

  6. 总结:强调事件循环的重要性

1.1 为什么需要事件循环?

核心答案 :JavaScript是单线程语言,事件循环机制让单线程能够非阻塞地处理高并发异步操作,这是现代Web应用响应的基石。

1.2 事件循环的官方定义

WHATWG规范定义:事件循环是一个持续运行的进程,它不断检查调用栈是否为空,为空时从任务队列中取出任务执行。

2.1 三驾马车架构

2.2 七大核心队列详解

现代浏览器中实际有多个任务队列,按优先级排序:

复制代码
// 1. 微任务队列 (Microtask Queue) - 最高优先级
Promise.resolve().then(() => console.log('微任务'));
queueMicrotask(() => console.log('微任务'));
MutationObserver回调

// 2. 交互任务队列 (User Interaction Queue) - 高优先级
// click、input、scroll等用户交互事件

// 3. 渲染任务队列 (Render Queue) - 每帧执行
requestAnimationFrame回调
// 注意:rAF在渲染前执行,不是宏任务也不是微任务

// 4. 定时器队列 (Timer Queue)
setTimeout(() => {}, 0);
setInterval(() => {}, 100);

// 5. 网络I/O队列 (Network Queue)
fetch().then(); // Promise回调是微任务,但触发在宏任务后
XMLHttpRequest回调

// 6. I/O任务队列 (I/O Queue)
FileReader、IndexedDB等

// 7. requestIdleCallback队列 (Idle Queue) - 最低优先级
requestIdleCallback(() => {
  // 空闲时执行
});

三、事件循环完整执行流程

3.1 单次循环(Tick)的详细步骤

3.2 渲染管道的执行时机

复制代码
// 演示渲染与事件循环的关系
function testRenderTiming() {
  console.log('1. 同步代码开始');
  
  // 微任务
  Promise.resolve().then(() => {
    console.log('2. 微任务执行');
    // 强制同步布局,可能引起强制同步布局(FSL)问题
    document.body.clientWidth; // 读取布局属性
  });
  
  // 宏任务
  setTimeout(() => {
    console.log('3. setTimeout执行');
    // 此时可能在新的一帧中
    requestAnimationFrame(() => {
      console.log('4. rAF回调执行(下一帧)');
    });
  }, 0);
  
  // requestAnimationFrame在当前帧
  requestAnimationFrame(() => {
    console.log('5. rAF回调执行(当前帧)');
  });
  
  console.log('6. 同步代码结束');
}

// 可能的输出顺序(取决于浏览器实现):
// 1. 同步代码开始
// 6. 同步代码结束
// 2. 微任务执行
// 5. rAF回调执行(当前帧)
// 3. setTimeout执行
// 4. rAF回调执行(下一帧)

四、关键特性与要点

4.1 微任务的「饥饿」现象

复制代码
// 微任务会在每个宏任务后立即执行,且会一直执行直到微任务队列为空
function microtaskStarvation() {
  console.log('开始');
  
  // 宏任务
  setTimeout(() => {
    console.log('setTimeout 1');
  }, 0);
  
  // 创建大量微任务
  Promise.resolve().then(() => {
    console.log('微任务 1');
    // 递归添加微任务
    Promise.resolve().then(() => {
      console.log('微任务 2');
      Promise.resolve().then(() => {
        console.log('微任务 3');
        // 更多微任务...
      });
    });
  });
  
  // 另一个宏任务
  setTimeout(() => {
    console.log('setTimeout 2');
  }, 0);
  
  console.log('结束');
  
  // 输出顺序:
  // 开始
  // 结束
  // 微任务 1
  // 微任务 2
  // 微任务 3
  // ...所有微任务执行完
  // setTimeout 1
  // setTimeout 2
}

4.2 任务队列的优先级策略

复制代码
// 不同任务类型的优先级演示
function demonstratePriorities() {
  // 1. 同步代码最高优先级
  console.log('1. 同步代码');
  
  // 2. 微任务队列
  Promise.resolve().then(() => console.log('2. 微任务'));
  
  // 3. 用户交互任务(模拟)
  button.addEventListener('click', () => {
    console.log('3. 点击事件(交互任务)');
    Promise.resolve().then(() => {
      console.log('4. 点击事件中的微任务');
    });
  });
  
  // 4. 定时器任务
  setTimeout(() => {
    console.log('5. setTimeout 0ms');
    Promise.resolve().then(() => {
      console.log('6. setTimeout中的微任务');
    });
  }, 0);
  
  // 5. 网络任务
  fetch('https://api.example.com')
    .then(() => console.log('7. fetch完成(微任务)'));
  
  // 6. requestAnimationFrame(在渲染阶段)
  requestAnimationFrame(() => {
    console.log('8. rAF回调');
  });
  
  // 7. requestIdleCallback(空闲时)
  requestIdleCallback(() => {
    console.log('9. 空闲回调');
  });
}

五、浏览器 vs Node.js 事件循环差异

5.1 架构对比

5.2 关键差异点

复制代码
// 1. process.nextTick vs queueMicrotask
// Node.js特有
process.nextTick(() => {
  console.log('nextTick - 比微任务优先级更高');
});

// 浏览器和Node.js通用
queueMicrotask(() => {
  console.log('queueMicrotask');
});

// 2. setImmediate (Node.js特有)
setImmediate(() => {
  console.log('setImmediate - check阶段执行');
});

// 3. I/O处理差异
const fs = require('fs');

// Node.js中,文件读取在poll阶段处理
fs.readFile('file.txt', () => {
  console.log('文件读取完成');
  
  setTimeout(() => {
    console.log('setTimeout in I/O callback');
  }, 0);
  
  setImmediate(() => {
    console.log('setImmediate in I/O callback');
    // 注意:在I/O回调中,setImmediate先于setTimeout执行
  });
});

六、性能优化与最佳实践

6.1 避免长任务(Long Tasks)

复制代码
// ❌ 坏实践:长时间阻塞主线程
function processLargeData() {
  const data = new Array(1000000).fill(0);
  // 长时间同步处理
  data.forEach((_, i) => {
    // 复杂计算...
    document.body.textContent = i; // 频繁DOM操作
  });
}

// ✅ 好实践:分片处理,让出控制权
async function processLargeDataOptimized() {
  const data = new Array(1000000).fill(0);
  
  function processChunk(start, end) {
    for (let i = start; i < end; i++) {
      // 处理数据...
    }
  }
  
  // 使用requestIdleCallback分片
  function processWithIdleCallback(index = 0) {
    const chunkSize = 1000;
    
    requestIdleCallback((deadline) => {
      while (index < data.length && deadline.timeRemaining() > 0) {
        processChunk(index, Math.min(index + chunkSize, data.length));
        index += chunkSize;
      }
      
      if (index < data.length) {
        processWithIdleCallback(index); // 继续处理
      }
    });
  }
  
  processWithIdleCallback();
}

6.2 合理使用任务队列

复制代码
// 选择合适的任务类型
function optimizeTaskScheduling() {
  // 1. 用户交互响应 - 使用微任务或requestAnimationFrame
  button.addEventListener('click', () => {
    // 立即响应
    button.classList.add('active');
    
    // 非关键更新用requestAnimationFrame
    requestAnimationFrame(() => {
      updateAnimation();
    });
    
    // 数据更新用微任务
    Promise.resolve().then(() => {
      updateData();
    });
  });
  
  // 2. 后台任务 - 使用requestIdleCallback
  function scheduleBackgroundWork() {
    requestIdleCallback((deadline) => {
      if (deadline.timeRemaining() > 5) {
        // 执行不超过5ms的任务
        performBackgroundTask();
      }
    }, { timeout: 2000 }); // 2秒后强制执行
  }
  
  // 3. 定时任务 - 使用setTimeout/setInterval
  const intervalId = setInterval(() => {
    if (shouldContinue()) {
      performRegularTask();
    } else {
      clearInterval(intervalId);
    }
  }, 1000);
}

1

相关推荐
爱笑的眼睛112 小时前
超越 `cross_val_score`:深度解析Scikit-learn交叉验证API的架构、技巧与陷阱
java·人工智能·python·ai
子春一22 小时前
Flutter 2025 可访问性(Accessibility)工程体系:从合规达标到包容设计,打造人人可用的数字产品
前端·javascript·flutter
白兰地空瓶2 小时前
别再只会调 API 了!LangChain.js 才是前端 AI 工程化的真正起点
前端·langchain
❀͜͡傀儡师3 小时前
SpringBoot 扫码登录全流程:UUID 生成、状态轮询、授权回调详解
java·spring boot·后端
jlspcsdn3 小时前
20251222项目练习
前端·javascript·html
a努力。3 小时前
国家电网Java面试被问:Spring Boot Starter 制作原理
java·spring boot·面试
一 乐3 小时前
酒店预约|基于springboot + vue酒店预约系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端
行走的陀螺仪4 小时前
Sass 详细指南
前端·css·rust·sass