Node系列 · Node基础:Node.js 概述
Node系列 · Node基础:Node.js 概述
Node.js 不是"跑在服务器上的 JavaScript",而是一套独立的运行时(Runtime)。理解它和浏览器的边界,就能解释为什么它能做高并发 IO、为什么不能做 CPU 密集型任务、为什么需要
package.json而浏览器页面不需要。
一、Node.js 是什么
Node.js 是基于 V8 引擎 + libuv + 一组内置核心模块(fs、http、net、crypto 等)的 JavaScript 运行时,主要用于在操作系统层执行 JavaScript 代码。
| 组件 | 角色 |
|---|---|
| V8 | Google 的 JS 引擎,负责把 JS 编译为机器码并执行。语法、数据类型、执行模型由 ECMAScript 规范定义,V8 是规范的实现 |
| libuv | 用 C 写的事件循环库;封装了异步 IO、线程池、信号、子进程;屏蔽操作系统差异 |
| 内置模块 | 用 C++ 实现,绑定到 JS 层,对外暴露 require('fs') 这类 API |
::: info
Node.js 不是"前端框架",也不属于任何一门后端语言。它是一个宿主环境——和浏览器宿主环境平行。浏览器宿主提供 DOM、BOM、Fetch;Node 宿主提供 fs、http、process。
:::
二、和浏览器运行时的核心差异
Node 运行时和浏览器运行时的差异,决定了它能做什么、不能做什么。这一节从 6 个核心维度对比:
| 维度 | 浏览器 | Node.js |
|---|---|---|
| 主要宿主对象 | window / document |
global / globalThis / process |
| 默认入口 | HTML + <script> 标签 |
.js 文件 + package.json 的 main |
| 内置模块 | DOM、BOM、Fetch、Storage | fs、http、net、path、crypto、stream |
| 模块系统 | ESM(<script type="module">) |
CommonJS(默认) + ESM(需 .mjs 或 package.json 加 "type": "module") |
| 多线程 | Web Worker(受限) | Worker Threads + 真正的多进程 |
| 进程控制 | 不能主动退出 | process.exit() / process.kill() / 子进程 |
| 权限模型 | 沙箱(同源策略) | 本机用户权限,文件 / 网络完全开放 |
::: warning
Node.js 拥有本机文件系统与网络权限。任何被执行的 JS 都可以读写硬盘、监听端口、调用子进程。 这一点与浏览器完全相反——不要把来源不明的 node script.js 当成"只是跑个 JS"。
:::
三、单线程与异步 IO
Node.js 主线程是单线程:JS 代码始终跑在同一个调用栈上。它能扛高并发的关键不是"CPU 多核",而是 IO 操作全部异步 + 事件循环调度。
关键结论:
- CPU 密集任务(同步循环、加密、压缩、JSON 序列化大对象)会阻塞整个事件循环,导致所有请求排队等待
- IO 密集任务(文件读写、网络请求、数据库查询)天然适合 Node——内核和线程池处理,主线程只关心回调
::: tip 一句话判断该不该用 Node
“请求量高、每次请求等待外部资源(DB / 第三方 API / 文件)多、本身的计算逻辑轻”——这种场景 Node 是首选。反过来,如果业务是图像处理、视频转码、复杂数学运算,应当把重计算交给专用服务,Node 只负责编排。
:::
基于上面的架构特点,可以反过来判断"什么场景 Node 不擅长",这是下一节的主题。
四、适用与不适用场景
适合
- Web API / BFF 层:Express、Koa、NestJS、Fastify 这类框架是 Node 最成熟的领域
- 实时通信:WebSocket 服务(聊天、协作、白板)
- 中间层 / 网关:聚合多个下游接口、做协议转换
- CLI 工具:前端构建工具(Vite、webpack)、脚手架(
create-vue、npm init) - 轻量脚本:爬虫、定时任务、数据迁移
不擅长
- CPU 重计算:图像处理、视频转码、科学计算、AI 推理。原因是主线程一旦被长任务占据,所有请求都得排队——CPU 密集场景的并发收益会被单线程结构吞掉
- 强事务一致性的金融核心系统:这类业务依赖成熟的分布式事务框架、关系型数据库生态、运维工具链,Java 系在该领域积累更深;Node 生态的相关基础设施相对薄弱,生产案例少
::: info
“不擅长"不是"不能做”。Node 完全可以通过 Worker Threads、子进程或外部服务调用来扩展能力——只是相比 Go、Rust、Java 这些原生支持并发的语言,性价比更低。
:::
五、一次完整的 Node 执行流程
console.log('1. 同步执行开始');
setTimeout(() => {
console.log('3. 宏任务回调(timers 阶段)');
}, 0);
Promise.resolve().then(() => {
console.log('2. 微任务(promise)');
});
console.log('4. 同步执行结束');
$ node hello.js
1. 同步执行开始
4. 同步执行结束
2. 微任务(promise)
3. 宏任务回调(timers 阶段)
事件循环按阶段推进,每个阶段处理一类宏任务,阶段切换前清空微任务:
每个节点的真实行为:
同步代码
JS 主线程自上而下立即执行的代码。这是整个流程唯一真正在 V8 上跑的环节。遇到 setTimeout、Promise、fs.readFile 等异步调用时不会阻塞——注册回调后立即返回。
进入循环前的微任务队列
同步代码全部跑完后、事件循环进入 timers 之前,会一次性清空微任务队列。process.nextTick 的优先级高于 Promise.then——同一轮里所有 nextTick 全部跑完,才会轮到 Promise。
process.nextTick(() => console.log('nextTick'));
Promise.resolve().then(() => console.log('promise'));
// 输出:nextTick → promise
timers 阶段
执行 setTimeout 和 setInterval 注册的回调。这里有两个隐藏约束:
- 最小延迟 1ms:即使写
setTimeout(fn, 0),也不会在 0ms 时触发,会被规范强制抬到 1ms - 不保证精确:轮询到 timers 时只取"已经到期"的回调;如果 poll 阶段耗时较长,下一次 timers 触发会跟着推迟
pending callbacks
执行延迟到下一个循环迭代的 I/O 回调(例如某些系统级错误回调)。日常业务代码几乎碰不到,多数 Node 开发者可以忽略此阶段。
idle, prepare
Node 内部使用的阶段,不对外暴露。libuv 在这里做一些空闲时的优化工作。用户代码不会在这里执行。
poll 阶段
事件循环最核心的阶段。它做两件事:
- 执行 poll 队列里已经到期的 I/O 回调(
fs.readFile、net的 data 事件等) - 若所有阶段都没有更多回调(timers 队列空、check 队列空、poll 队列空),poll 会阻塞等待新 I/O
阻塞等待不是 bug,而是设计——Node 不会在无事可做时浪费 CPU。只有当这些条件之一满足时,poll 才会停止等待:
- timers 队列有到期回调
setImmediate注册了新任务- poll 队列本身有新回调
check 阶段
执行 setImmediate 注册的回调。这是 Node 提供的"下一轮立即执行"机制——不经过 timers 的 1ms 最小延迟。
setImmediate 经常和 setTimeout(fn, 0) 拿来比较:在 I/O 回调内部,前者总是比后者先触发,因为 poll 阶段之后紧跟 check,而 timers 要等到下一轮。
close callbacks
执行 socket.on('close', ...)、process.exit() 后的清理回调等关闭事件。
阶段后的微任务清空
每个宏任务阶段执行完毕、即将进入下一阶段时,会再清空一次微任务队列。这保证了微任务总是优先于下一个宏任务阶段。
循环判断
如果 timers、check、poll 三个队列都为空,事件循环就退出,进程随之结束。常见的退出场景:
- 所有异步 I/O 完成 + 没有定时器 + 没有 setImmediate
- 显式调用
process.exit()
这个模型会贯穿后续所有章节——理解它,就理解了为什么 setImmediate 不一定比 setTimeout(fn, 0) 快、为什么文件读取回调默认在 poll 阶段触发。
六、Node.js 版本选择
| 版本 | 状态 | 何时用 |
|---|---|---|
| Current(如 24.x) | 当前开发版 | 想用最新语法、fetch、内置测试运行器 |
| LTS(如 22.x Iron) | 长期维护,推荐 | 生产环境、本系列全部示例 |
| EOL | 已停止维护 | 升级 |
本文所有示例在 Node.js 22 LTS 验证通过;除明确标注外,不依赖任何实验性 API。
七、小结
- Node.js = V8 + libuv + 内置模块,是 JS 的服务端运行时,不是后端语言
- 单线程 + 异步 IO + 事件循环是其架构核心;CPU 密集场景需要外移
- 拥有本机权限,不要把
node script.js当成"沙箱里跑 JS" - 选择版本优先 LTS;本文使用 Node 22
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)