文章目錄
- 前言
前言
結合 Node.js 的核心機制進行說明:
-
解釋事件循環的各個階段。
答案
Node.js 事件循環分為 6 個階段,按順序執行: -
Timers:執行
setTimeout
和setInterval
的回調。 -
Pending I/O Callbacks:處理系統操作(如 TCP 錯誤)的回調。
-
Idle/Prepare:Node.js 內部使用的階段。
-
Poll:
? 檢索新的 I/O 事件并執行回調(如文件讀取、HTTP 請求)。? 如果 Poll 隊列為空:
? 若有
setImmediate
回調,進入 Check 階段。? 否則等待新的 I/O 事件。
-
Check:執行
setImmediate
的回調。 -
Close Callbacks:處理關閉事件的回調(如
socket.on('close')
)。
解析
? 每個階段都是一個 FIFO 隊列,必須清空當前階段的回調才會進入下一階段。
? 重點:setTimeout
和 setInterval
的回調不一定精確按時執行,因為 Poll 階段可能阻塞事件循環。
setImmediate
和setTimeout(fn, 0)
的區別是什么?
答案
? 執行順序:
? 在主模塊中,兩者的執行順序不確定(受進程性能影響)。
? 在 I/O 回調(如 fs.readFile
)中,setImmediate
總是先于 setTimeout
。
? 底層階段:
? setImmediate
在 Check 階段 執行。
? setTimeout
在 Timers 階段 執行。
示例代碼
fs.readFile('file.txt', () => {setTimeout(() => console.log('Timeout'), 0);setImmediate(() => console.log('Immediate'));
});
// 輸出順序:Immediate → Timeout
解析
? 在 I/O 回調中,事件循環處于 Poll 階段,執行完回調后優先進入 Check 階段(setImmediate
),再進入 Timers 階段。
- 什么是事件驅動編程?Node.js 如何實現非阻塞 I/O?
答案
? 事件驅動:通過監聽事件(如點擊、文件讀取完成)觸發回調,而非主動輪詢。
? 非阻塞 I/O 的實現:
? 操作系統級異步:網絡請求等由內核異步處理(通過 epoll
、kqueue
)。
? 線程池:文件 I/O 等阻塞操作由 libuv 的線程池處理,完成后通知主線程。
解析
? Node.js 的單線程僅指 JS 主線程,底層通過多線程 + 事件循環實現高并發。
- 如何監控和調試內存泄漏?
答案
常見泄漏場景:
? 未清理的全局變量、閉包引用、定時器、事件監聽器(如 EventEmitter
)。
調試工具:
? Chrome DevTools 的 Heap Snapshot 對比內存快照。
? 使用 --inspect
參數 + node-heapdump
模塊生成堆內存快照。
? 監控 process.memoryUsage()
。
解析
? 內存泄漏的本質是對象被意外保留,無法被 GC 回收。
process.nextTick
和setImmediate
的執行順序?
答案
?process.nextTick
:
? 在事件循環的每個階段結束后立即執行(微任務)。
? 優先級高于 Promise.then()
。
? setImmediate
:
? 在 Check 階段執行(宏任務)。
執行順序:
Promise.resolve().then(() => console.log('Promise'));
process.nextTick(() => console.log('nextTick'));
setImmediate(() => console.log('Immediate'));
// 輸出順序:nextTick → Promise → Immediate
解析
? process.nextTick
會將回調插入當前階段末尾,而 setImmediate
是下一輪循環。
- Node.js 單線程模型如何處理并發請求?
答案
? 非阻塞 I/O:主線程發起異步 I/O 操作后繼續處理其他任務,I/O 完成后通過事件循環觸發回調。
? 線程池:文件操作等阻塞任務由 libuv 的線程池處理(默認 4 個線程)。
解析
? 單線程避免了多線程的鎖競爭和上下文切換開銷,適合 I/O 密集型場景,但不適合 CPU 密集型任務。
- Cluster 模塊是如何工作的?
答案
? 原理:Master 進程創建多個子進程(Worker),共享同一端口,通過輪詢(Round-Robin)分配請求。
? 代碼示例:
const cluster = require('cluster');
if (cluster.isMaster) {for (let i = 0; i < 4; i++) cluster.fork(); // 啟動 4 個 Worker
} else {require('./app.js'); // 每個 Worker 運行一個服務實例
}
解析
? 子進程通過 IPC(進程間通信)與 Master 進程通信。
? 優勢:利用多核 CPU,提高吞吐量。
- Buffer 和 Stream 的應用場景是什么?
答案
? Buffer:處理二進制數據(如圖片、文件),避免字符串轉換的性能損耗。
? Stream:
? 大文件處理:分片讀取文件,避免內存溢出(如 fs.createReadStream
)。
? 實時數據傳輸:HTTP 響應、TCP 套接字。
示例
// 使用 Stream 復制文件
fs.createReadStream('input.txt').pipe(fs.createWriteStream('output.txt'));
解析
? Stream 通過事件分塊處理數據,顯著降低內存占用。
總結
掌握這些問題的核心原理(事件循環、異步 I/O、內存管理)能讓你在面試中脫穎而出。建議結合以下實踐:
- 使用
node --trace-event-categories=node.async_hooks
跟蹤異步事件。 - 閱讀 libuv 文檔 和 Node.js 官方博客。
- 通過
WARTHOG
(Node.js 性能分析工具)定位性能瓶頸。