Node.js 运行时架构
解释 Node.js 如何由 V8、Node API、C++ bindings、libuv 与操作系统协作,形成单主线程事件循环模型。
#type / concept
#status / growing
#tech / dev / backend
#resource / nodejs
#resource / javascript
[!info] related notes
- 所属 MOC: Node.js MOC, javascript-in-nodejs-moc
- 上位主题: nodejs, Node.js 下的 JavaScript
- 背景说明: Node.js 的起源与设计目标
- 执行引擎: V8
- 调度核心: libuv
- 相关概念: Node.js 事件循环阶段, libuv 事件循环与 Worker Pool, worker-threads, Stream
- 关系笔记: process.nextTick、Promise、setImmediate 与 setTimeout 的关系
Node.js 运行时架构
一句话定义
Node.js 的运行时架构可以概括成:
JavaScript 代码由 V8 执行,异步 I/O 和事件循环主要由 libuv 调度,Node 再通过宿主 API 把文件、网络、进程和流式能力暴露给业务代码。
核心机制 / 工作原理
可以把它看成四层:
JavaScript 业务代码
↓
Node.js API:fs / http / net / stream / crypto / process
↓
V8 + C++ bindings + libuv
↓
操作系统:文件、网络、进程、线程、时钟
1. V8 负责什么
- 执行 JavaScript 代码
- 管理对象、闭包、作用域和 GC
- 把你的业务逻辑跑在主线程调用栈里
如果要把引擎本身单独看清,可以继续读 V8。
2. Node.js API 负责什么
- 暴露服务端常用能力
- 把 JavaScript 层的调用桥接到底层实现
- 提供统一编程模型,例如
fs、http、Buffer、Stream
3. libuv 负责什么
- 事件循环
- 定时器调度
- 一部分异步 I/O 协调
- 线程池任务分发
它也是为什么 Node.js 能把很多 I/O 型等待从主线程里拿走。
如果要继续细看 poll、worker pool 和 handle/request 生命周期,看 libuv 和 libuv 事件循环与 Worker Pool。
4. 操作系统负责什么
- 文件读写
- 网络连接
- 进程与线程
- 时钟与信号
Node.js 不是自己“发明”了异步 I/O,而是把操作系统能力封装成更适合 JavaScript 的宿主模型。
最小例子 / 最小场景
当你写出这样一段代码:
import { readFile } from 'node:fs/promises';
const content = await readFile('./a.txt', 'utf8');
console.log(content);
大致发生的是:
- JavaScript 代码在 V8 主线程里运行
readFile通过 Node API 进入底层- 底层把文件读取交给 libuv / 操作系统
- 主线程先继续空出来处理别的任务
- 文件读取完成后,再把后续逻辑调回事件循环继续执行
边界与易混淆点
Node.js 是单线程的吗
更准确的说法是:
- JavaScript 业务执行主线程通常是单线程
- 但 Node.js 整体不是“只有一个线程”
- libuv 有线程池
- V8 也有后台线程
所以它更接近:
单主线程事件循环模型,而不是“整个运行时只有一条线程”。
为什么适合 I/O 密集型
因为大量等待数据库、网络、文件返回的时间,不需要一直卡住主线程。
为什么不适合 CPU 密集型
因为你的 JavaScript 计算本身还是跑在主线程里。只要计算持续太久,整个事件循环就会被堵住。