一、基础概念与历史背景
1.1 为什么需要事件循环?
JavaScript 最初被设计为浏览器脚本语言,其单线程特性源于其主要用途:处理用户交互和操作DOM。如果没有事件循环机制,将会面临以下问题:
-
阻塞问题
- 同步操作会阻塞UI渲染
- 长时间运算会导致页面无响应
- 网络请求等IO操作会造成等待
-
资源利用
- CPU在等待IO时完全空闲
- 无法充分利用多核处理器
- 内存使用效率低下
-
用户体验
- 界面卡顿
- 交互延迟
- 动画不流畅
1.2 进程与线程
// 进程与线程的关系示例
浏览器进程 {
GUI渲染线程
JS引擎线程
事件触发线程
定时触发器线程
异步HTTP请求线程
...
}
1.2.1 浏览器多进程架构
- Browser进程:浏览器的主进程
- Renderer进程:渲染进程,每个标签页独立
- GPU进程:处理GPU任务
- Network进程:处理网络请求
- Plugin进程:插件进程
1.2.2 渲染进程的多线程
-
GUI渲染线程
- 负责渲染页面
- 解析HTML、CSS
- 构建DOM树和渲染树
-
JS引擎线程
- 解析执行JavaScript代码
- V8引擎的主线程
- 与GUI渲染线程互斥
-
事件触发线程
- 管理事件队列
- 协调事件循环
- 分发事件到JS引擎线程
-
定时器触发线程
- 管理setTimeout/setInterval
- 计时完毕后将回调加入事件队列
-
异步HTTP请求线程
- 处理XMLHttpRequest
- 处理Fetch请求
- 请求完成后将回调加入事件队列
1.3 JavaScript的运行时环境
// JavaScript运行时环境示意
JavaScript Runtime = {
调用栈(Call Stack),
堆内存(Heap),
任务队列(Task Queue) {
宏任务队列(Macrotask Queue),
微任务队列(Microtask Queue)
},
Web APIs {
DOM,
AJAX,
setTimeout,
...
}
}
1.3.1 内存模型
// 内存分配示例
// 栈内存
let a = 1;
let b = 'string';
let c = true;
// 堆内存
let obj = {
name: 'object',
data: [1, 2, 3]
};
let arr = new Array(1000);
1.3.2 执行上下文
// 执行上下文示例
// 全局执行上下文
var global = 'global';
function outer() {
// outer函数执行上下文
var a = 1;
function inner() {
// inner函数执行上下文
var b = 2;
console.log(a, b);
}
inner();
}
outer();
二、事件循环的核心机制
2.1 调用栈详解
// 调用栈运行机制示例
function multiply(a, b) {
return a * b;
}
function square(n) {
return multiply(n, n);
}
function printSquare(n) {
var squared = square(n);
console.log(squared);
}
printSquare(4);
// 调用栈变化过程
// 1. [printSquare]
// 2. [printSquare, square]
// 3. [printSquare, square, multiply]
// 4. [printSquare, square]
// 5. [printSquare]
// 6. []
2.1.1 调用栈溢出
// 栈溢出示例
function recursion() {
recursion();
}
// 防止栈溢出的解决方案
function safeRecursion(n) {
if (n === 0) return;
// 使用setTimeout将递归放入宏任务队列
setTimeout(() => {
console.log(n);
safeRecursion(n - 1);
}, 0);
}
safeRecursion(10000); // 不会栈溢出
2.2 任务队列详解
2.2.1 宏任务(Macrotask)完整列表
// 宏任务类型及其优先级
宏任务优先级 = {
1: 'script(整体代码)',
2: 'setTimeout/setInterval',
3: 'setImmediate(Node环境)',
4: 'requestAnimationFrame(浏览器环境)',
5: 'I/O操作',
6: 'UI rendering',
7: 'MessageChannel',
8: 'setImmediate(IE专用)'
}
2.2.2 微任务(Microtask)完整列表
// 微任务类型及其优先级
微任务优先级 = {
1: 'process.nextTick(Node环境)',
2: 'Promise.then/catch/finally',
3: 'queueMicrotask',
4: 'MutationObserver',
5: 'IntersectionObserver',
6: 'ResizeObserver'
}
三、事件循环的详细执行流程
3.1 完整的事件循环流程图
// 事件循环伪代码表示
while (true) {
// 1. 执行同步代码,直到调用栈清空
executeSynchronousCode();
// 2. 执行微任务队列的所有任务
while (microTaskQueue.length > 0) {
executeMicroTask(microTaskQueue.dequeue());
}
// 3. 执行一个宏任务
if (macroTaskQueue.length > 0) {
executeMacroTask(macroTaskQueue.dequeue());
}
// 4. 如果需要,进行UI渲染
if (shouldRender) {
render();
}
}
3.2 实际案例分析
3.2.1 基础案例
console.log('1'); // 同步代码
setTimeout(() => {
console.log('2'); // 宏任务
}, 0);
Promise.resolve().then(() => {
console.log('3'); // 微任务
});
console.log('4'); // 同步代码
// 输出顺序:1 -> 4 -> 3 -> 2
3.2.2 复杂案例
console.log('1'); // 同步
setTimeout(() => {
console.log('2'); // 宏任务1
Promise.resolve().then(() => {
console.log('3'); // 宏任务1中的微任务
});
}, 0);
new Promise((resolve) => {
console.log('4'); // 同步
resolve();
}).then(() => {
console.log('5'); // 微任务1
setTimeout(() => {
console.log('6'); // 微任务1中的宏任务
}, 0);
});
console.log('7'); // 同步
// 输出顺序:1 -> 4 -> 7 -> 5 -> 2 -> 3 -> 6
3.3 特殊场景分析
3.3.1 嵌套的微任务
Promise.resolve().then(() => {
console.log('1'); // 第一个微任务
Promise.resolve().then(() => {
console.log('2'); // 嵌套的微任务
});
}).then(() => {
console.log('3'); // 第二个微任务
});
// 输出顺序:1 -> 2 -> 3
3.3.2 微任务与DOM更新
// DOM更新与微任务的关系
const div = document.createElement('div');
div.style.width = '100px';
document.body.appendChild(div);
Promise.resolve().then(() => {
console.log(div.offsetWidth); // 100
div.style.width = '200px';
console.log(div.offsetWidth); // 100
setTimeout(() => {
console.log(div.offsetWidth); // 200
}, 0);
});
四、Promise与异步编程
4.1 Promise的实现原理
// 简化版Promise实现
class MyPromise {
constructor(executor) {
this.status = 'pending';
this.value = undefined;
this.reason = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.status === 'pending') {
this.status = 'fulfilled';
this.value = value;
this.onFulfilledCallbacks.forEach(fn => fn());
}
};
const reject = (reason) => {
if (this.status === 'pending') {
this.status = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn());
}
};
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
const promise2 = new MyPromise((resolve, reject) => {
if (this.status === 'fulfilled') {
queueMicrotask(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
}
if (this.status === 'rejected') {
queueMicrotask(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
}
if (this.status === 'pending') {
this.onFulfilledCallbacks.push(() => {
queueMicrotask(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
this.onRejectedCallbacks.push(() => {
queueMicrotask(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}
});
return promise2;
}
}
4.2 Promise的常见应用模式
4.2.1 Promise.all 实现
Promise.myAll = function(promises) {
return new Promise((resolve, reject) => {
let results = [];
let completed = 0;
if (promises.length === 0) {
resolve(results);
return;
}
promises.forEach((promise, index) => {
Promise.resolve(promise).then(
value => {
results[index] = value;
completed++;
if (completed === promises.length) {
resolve(results);
}
},
reason => {
reject(reason);
}
);
});
});
};
4.2.2 Promise.race 实现
Promise.myRace = function(promises) {
return new Promise((resolve, reject) => {
promises.forEach(promise => {
Promise.resolve(promise).then(resolve, reject);
});
});
};
五、Node.js事件循环详解
5.1 Node.js事件循环的六个阶段
// Node.js事件循环的阶段顺序
const phases = {
1: 'timers', // setTimeout, setInterval
2: 'pending callbacks', // 执行延迟到下一个循环迭代的 I/O 回调
3: 'idle, prepare', // 仅系统内部使用
4: 'poll', // 检索新的 I/O 事件,执行 I/O 相关的回调
5: 'check', // setImmediate() 回调
6: 'close callbacks' // 关闭的回调函数
};
5.2 Node.js与浏览器的事件循环区别
5.2.1 微任务执行时机
// 浏览器环境
setTimeout(() => {
console.log('timeout1');
Promise.resolve().then(() => console.log('promise1'));
}, 0);
setTimeout(() => {
console.log('timeout2');
Promise.resolve().then(() => console.log('promise2'));
}, 0);
// 浏览器输出:timeout1 -> promise1 -> timeout2 -> promise2
// Node.js环境(v11之前)
setTimeout(() => {
console.log('timeout1');
Promise.resolve().then(() => console.log('promise1'));
}, 0);
setTimeout(() => {
console.log('timeout2');
Promise.resolve().then(() => console.log('promise2'));
}, 0);
// Node.js输出(v11之前):timeout1 -> timeout2 -> promise1 -> promise2
5.2.2 process.nextTick
// process.nextTick 优先级演示
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
process.nextTick(() => console.log('nextTick'));
Promise.resolve().then(() => console.log('promise'));
// 输出顺序:
// 1. nextTick
// 2. promise
// 3. timeout
// 4. immediate
5.3 Node.js性能优化
// 优化示例:避免阻塞事件循环
// 不好的实践
function processLargeArray(array) {
array.forEach(item => {
// 耗时操作
heavyComputation(item);
});
}
// 优化后的实践
async function processLargeArray(array) {
const chunkSize = 1000;
for (let i = 0; i < array.length; i += chunkSize) {
const chunk = array.slice(i, i + chunkSize);
await new Promise(resolve => setImmediate(resolve));
chunk.forEach(item => heavyComputation(item));
}
}
六、面试题解析
6.1 经典面试题
6.1.1 输出顺序题
// 问题1:以下代码输出顺序是什么?
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(() => {
console.log('setTimeout');
}, 0);
async1();
new Promise((resolve) => {
console.log('promise1');
resolve();
}).then(() => {
console.log('promise2');
});
console.log('script end');
// 输出顺序:
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeout
6.1.2 Promise相关题
// 问题2:以下代码输出什么?
Promise.resolve().then(() => {
console.log('then1');
Promise.resolve().then(() => {
console.log('then1-1');
});
}).then(() => {
console.log('then2');
});
Promise.resolve().then(() => {
console.log('then3');
});
// 输出顺序:
// then1
// then3
// then1-1
// then2
6.2 进阶面试题
6.2.1 实现限制并发的Promise调度器
class Scheduler {
constructor(limit) {
this.queue = [];
this.limit = limit;
this.running = 0;
}
add(promiseCreator) {
return new Promise((resolve) => {
this.queue.push({
promiseCreator,
resolve
});
this.run();
});
}
run() {
if (this.running >= this.limit || this.queue.length === 0) {
return;
}
this.running++;
const { promiseCreator, resolve } = this.queue.shift();
promiseCreator().then((result) => {
resolve(result);
this.running--;
this.run();
});
}
}
// 使用示例
const scheduler = new Scheduler(2);
const addTask = (time, value) => {
scheduler.add(() =>
new Promise(resolve => {
setTimeout(() => {
console.log(value);
resolve();
}, time);
})
);
};
addTask(1000, '1');
addTask(500, '2');
addTask(300, '3');
addTask(400, '4');
// 输出: 2 -> 1 -> 3 -> 4
6.2.2 手写防抖节流
// 防抖
function debounce(fn, delay) {
let timer = null;
return function(...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
// 节流
function throttle(fn, delay) {
let last = 0;
return function(...args) {
const now = Date.now();
if (now - last > delay) {
last = now;
fn.apply(this, args);
}
};
}
// 使用示例
const debouncedScroll = debounce(() => {
console.log('scroll');
}, 200);
const throttledScroll = throttle(() => {
console.log('scroll');
}, 200);
window.addEventListener('scroll', debouncedScroll);
window.addEventListener('scroll', throttledScroll);
6.3 实战面试题
6.3.1 实现一个带并发限制的异步调度器
class AsyncPool {
constructor(limit) {
this.limit = limit;
this.running = 0;
this.queue = [];
}
async add(fn) {
if (this.running >= this.limit) {
await new Promise(resolve => this.queue.push(resolve));
}
this.running++;
try {
return await fn();
} finally {
this.running--;
if (this.queue.length > 0) {
this.queue.shift()();
}
}
}
}
// 使用示例
const pool = new AsyncPool(2);
const sleep = (time) => new Promise(resolve => setTimeout(resolve, time));
async function test() {
console.time('total');
const tasks = [
() => sleep(1000).then(() => console.log(1)),
() => sleep(500).then(() => console.log(2)),
() => sleep(300).then(() => console.log(3)),
() => sleep(400).then(() => console.log(4))
];
await Promise.all(tasks.map(task => pool.add(task)));
console.timeEnd('total');
}
test();
七、性能优化建议
7.1 代码层面优化
- 合理使用微任务和宏任务
// 不好的实践
function processData(items) {
items.forEach(item => {
setTimeout(() => {
// 处理逻辑
}, 0);
});
}
// 好的实践
async function processData(items) {
const chunks = chunk(items, 1000);
for (const chunk of chunks) {
await new Promise(resolve => setTimeout(resolve, 0));
chunk.forEach(item => {
// 处理逻辑
});
}
}
- 避免长时间占用事件循环
// 不好的实践
function heavyComputation() {
for (let i = 0; i < 1000000; i++) {
// 复杂计算
}
}
// 好的实践
async function heavyComputation() {
const total = 1000000;
const batchSize = 1000;
for (let i = 0; i < total; i += batchSize) {
await new Promise(resolve => setTimeout(resolve, 0));
for (let j = 0; j < batchSize; j++) {
// 复杂计算
}
}
}
7.2 工程实践建议
- 使用Performance API监控性能
- 合理划分任务优先级
- 利用Web Workers处理密集计算
- 采用虚拟列表优化大数据渲染
- 使用RequestAnimationFrame优化动画
八、总结
事件循环是JavaScript实现异步编程的核心机制,深入理解它对于:
- 编写高性能代码
- 解决复杂异步问题
- 优化用户体验
都有重要意义。
建议读者:
- 多写demo验证概念
- 关注各种框架的异步实现
- 持续学习新的异步编程模式
写在最后
感谢您花时间阅读这篇文章! 🙏
如果您觉得文章对您有帮助,欢迎:
- 关注我的技术博客:徐白知识星球 📚
- 关注微信公众号:徐白知识星球
我会持续输出高质量的前端技术文章,分享实战经验和技术心得。
共同成长
- 欢迎在评论区留言交流
- 有问题随时后台私信
- 定期举办技术分享会
- 更多精彩内容持续更新中…
让我们一起在技术的道路上携手前行! 💪