面试题答案
一键面试事件循环基本原理
- 单线程执行环境:Node.js 运行在单线程环境中,这意味着同一时间只能执行一个任务。
- 调用栈:它是一个后进先出(LIFO)的数据结构,用于存储正在执行的函数调用。当一个函数被调用时,它的执行上下文会被压入调用栈;函数执行完毕后,其上下文从调用栈弹出。
- 任务队列:也叫消息队列,用于存放异步操作完成后产生的回调函数。当一个异步操作(如 I/O 操作、定时器到期等)完成时,对应的回调函数会被放入任务队列。
- 事件循环机制:事件循环不断地检查调用栈是否为空。如果调用栈为空,它会从任务队列中取出一个回调函数并将其压入调用栈执行。如此循环往复,使得 Node.js 能够在单线程环境下处理多个异步任务。
对 Node HTTP 服务器处理并发请求的帮助
- 非阻塞 I/O:Node HTTP 服务器基于事件驱动和非阻塞 I/O 模型。当接收到一个 HTTP 请求时,服务器不会等待请求处理完成(如读取文件、数据库查询等 I/O 操作),而是将这些 I/O 操作交给底层系统去处理,然后继续处理下一个请求。
- 异步回调处理:当 I/O 操作完成后,其对应的回调函数会被放入任务队列。事件循环会在调用栈为空时,将这些回调函数依次压入调用栈执行,从而完成对请求的响应。这样,Node HTTP 服务器可以高效地处理多个并发请求,而不会因为某个请求的 I/O 操作而阻塞其他请求的处理。
可能阻塞事件循环的操作及避免方法
- 阻塞操作示例:同步的文件读取操作(如
fs.readFileSync
)。如果在 Node HTTP 服务器的请求处理函数中使用fs.readFileSync
读取一个大文件,由于该操作是同步的,会一直占用调用栈,直到文件读取完成,期间事件循环无法处理其他任务,导致服务器暂时无法响应新的请求。 - 避免方法:使用异步版本的文件读取操作,如
fs.readFile
。这个方法是非阻塞的,它会将文件读取操作交给底层系统,然后立即返回,不会阻塞调用栈。当文件读取完成后,通过回调函数来处理读取结果。示例代码如下:
const fs = require('fs');
const http = require('http');
http.createServer((req, res) => {
fs.readFile('largeFile.txt', 'utf8', (err, data) => {
if (err) {
res.statusCode = 500;
res.end('Error reading file');
} else {
res.end(data);
}
});
}).listen(3000);
这样,在文件读取期间,事件循环可以继续处理其他请求,保证了服务器的并发处理能力。