-
简要介绍:什么是事件循环?为什么需要事件循环?
-
核心概念:调用栈、任务队列(宏任务和微任务)、渲染步骤。
-
详细流程:描述事件循环的循环过程,包括如何执行宏任务和微任务。
-
举例说明:通过代码示例展示不同任务的执行顺序。
-
扩展话题:Node.js事件循环与浏览器事件循环的区别,requestAnimationFrame,requestIdleCallback等。
-
总结:强调事件循环的重要性
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