在浏览器和 Node.js 中,事件循环的机制有一些相似之处,但也有显著的区别。今天主要从图形的角度谈一下个人对这两个事件循环的理解,不喜1楼喷
对于文字版本感兴趣可以移步@从面试中理解浏览器和node环境中的事件循环瞅瞅
上正文!
事件循环在 Node.js 和浏览器中的特性对比
下面是一个详细的对比表格,展示了 Node.js 和浏览器中的事件循环的主要区别和相似点:
| 特性/方面 | Node.js 事件循环 | 浏览器事件循环 |
|---|---|---|
| 执行环境 | 服务器端环境,专为高性能 I/O 操作设计 | 客户端环境,专注于用户交互和界面渲染 |
| 事件循环阶段 | 1. Timers 2. I/O callbacks 3. Idle, prepare 4. Poll 5. Check 6. Close callbacks | 1. Macro-tasks (如 setTimeout, setInterval) 2. Micro-tasks (如 Promises, MutationObserver) |
| Timers | setTimeout 和 setInterval 在 Timers 阶段执行 | setTimeout 和 setInterval 在 Macro-tasks 阶段执行 |
| Micro-tasks | process.nextTick 和 Promise 回调在每个阶段结束后执行 | Promise 回调和 MutationObserver 在 Macro-tasks 阶段之后执行 |
| I/O callbacks | 处理大部分的回调,比如网络请求的回调 | 主要处理用户交互和网络请求的回调 |
| Poll 阶段 | 处理新的 I/O 事件,执行 I/O 相关的回调,几乎所有的回调都在这个阶段被执行 | 浏览器没有明确的 Poll 阶段,I/O 操作通常通过事件机制处理 |
| Check 阶段 | 专门处理 setImmediate 的回调 | 浏览器没有 setImmediate |
| Idle, prepare 阶段 | 内部使用,通常不涉及用户代码 | 浏览器没有对应的阶段 |
| Close callbacks | 处理 close 事件的回调,例如 socket.on('close', ...) | 浏览器没有明确的 Close callbacks 阶段 |
| 渲染 | 不涉及 UI 渲染 | 包含 UI 渲染,通常在事件循环的每个循环结束时进行 |
| 动画帧 | 不涉及 | requestAnimationFrame 用于优化动画渲染 |
| 典型用例 | 处理高并发 I/O 操作,如文件系统操作、网络请求等 | 处理用户交互、DOM 操作、动画等 |
为了清晰地展示这些机制,我们可以画两张思维导图对比一下。
浏览器环境中的事件循环思维导图
Node.js 环境中的事件循环思维导图
浏览器环境中的事件循环流程图
Node.js 环境中的事件循环流程图
Node.js 示例代码解析
下面我们通过一个示例代码来展示 Node.js 中事件循环的工作机制:
const fs = require('fs');
const https = require('https');
// 模拟空闲和准备阶段的操作
function idlePrepareSimulation() {
// 这是一个示例,Node.js 内部使用的阶段,通常不需要显式处理
}
// 读取文件内容
fs.readFile('data.txt', 'utf8', (err, data) => {
if (err) {
console.error('Error reading file:', err);
return;
}
console.log('File content read:', data);
// 数据处理
const processedData = processData(data);
console.log('Data processed:', processedData);
// 网络请求
makeNetworkRequest(processedData, (response) => {
console.log('Network request response:', response);
// 进一步处理网络响应
handleNetworkResponse(response);
});
// 设置一个定时器
setTimeout(() => {
console.log('setTimeout inside fs.readFile');
}, 0);
// 设置一个立即执行的回调
setImmediate(() => {
console.log('setImmediate inside fs.readFile');
});
// 设置一个下一个 tick 执行的回调
process.nextTick(() => {
console.log('nextTick inside fs.readFile');
});
});
// 定时器阶段
setTimeout(() => {
console.log('setTimeout 1');
process.nextTick(() => {
console.log('nextTick inside setTimeout 1');
});
}, 0);
setTimeout(() => {
console.log('setTimeout 2');
}, 0);
// 检查阶段
setImmediate(() => {
console.log('setImmediate 1');
process.nextTick(() => {
console.log('nextTick inside setImmediate 1');
});
});
setImmediate(() => {
console.log('setImmediate 2');
});
// 定时器阶段的循环定时器
const intervalId = setInterval(() => {
console.log('setInterval');
clearInterval(intervalId); // 只执行一次
}, 1000);
// 关闭回调
const server = https.createServer((req, res) => {
res.end('Hello World');
});
server.listen(3000, () => {
console.log('Server listening on port 3000');
});
// 关闭服务器
server.close(() => {
console.log('Server closed');
});
function processData(data) {
// 模拟数据处理
return data.toUpperCase();
}
function makeNetworkRequest(data, callback) {
const options = {
hostname: 'jsonplaceholder.typicode.com',
path: '/posts/1',
method: 'GET'
};
const req = https.request(options, (res) => {
let responseData = '';
res.on('data', (chunk) => {
responseData += chunk;
});
res.on('end', () => {
callback(responseData);
});
});
req.on('error', (e) => {
console.error('Problem with request:', e.message);
});
req.end();
}
function handleNetworkResponse(response) {
// 模拟处理网络响应
console.log('Handling network response:', response);
}
Node.js 示例代码的泳道图
为了更好地理解上述代码在事件循环中的执行顺序,我们可以使用一张泳道图来展示:
巴拉巴拉其他任务代指用户能介入的阶段
代码解析
在上述代码中,我们展示了 Node.js 中事件循环的各个阶段。以下是关键点:
- 文件读取 (
fs.readFile):文件读取完成后,会依次执行process.nextTick、setTimeout和setImmediate回调。 - 定时器阶段 (
setTimeout):定时器回调会按照设定的时间执行,并在执行后立刻触发process.nextTick回调。 - 检查阶段 (
setImmediate):setImmediate回调会在当前事件循环的check阶段执行,并在执行后立刻触发process.nextTick回调。 - I/O 操作 (
https.request):网络请求的回调在poll阶段执行,并在执行后处理响应数据。 - 关闭回调 (
server.close):关闭服务器时,触发close回调。
通过这个示例,我们可以更好地理解 Node.js 中事件循环的各个阶段及其执行顺序。
总结
通过对比 Node.js 和浏览器中的事件循环机制,我们可以看到它们在设计上的差异和各自的优势。Node.js 更加专注于高性能 I/O 操作,而浏览器则更加注重用户交互和界面渲染。希望这篇文章能帮助你更好地理解事件循环的工作原理,我们下期再见~