Skip to content

Node系列 · Node基础:Node.js 概述

Node.js 不是"跑在服务器上的 JavaScript",而是一套独立的运行时(Runtime)。理解它和浏览器的边界,就能解释为什么它能做高并发 IO、为什么不能做 CPU 密集型任务、为什么需要 package.json 而浏览器页面不需要。

一、Node.js 是什么

Node.js 是基于 V8 引擎 + libuv + 一组内置核心模块(fshttpnetcrypto 等)的 JavaScript 运行时,主要用于在操作系统层执行 JavaScript 代码。

组件角色
V8Google 的 JS 引擎,负责把 JS 编译为机器码并执行。语法、数据类型、执行模型由 ECMAScript 规范定义,V8 是规范的实现
libuv用 C 写的事件循环库;封装了异步 IO、线程池、信号、子进程;屏蔽操作系统差异
内置模块用 C++ 实现,绑定到 JS 层,对外暴露 require('fs') 这类 API

INFO

Node.js 不是"前端框架",也不属于任何一门后端语言。它是一个宿主环境——和浏览器宿主环境平行。浏览器宿主提供 DOM、BOM、Fetch;Node 宿主提供 fshttpprocess

二、和浏览器运行时的核心差异

Node 运行时和浏览器运行时的差异,决定了它能做什么、不能做什么。这一节从 6 个核心维度对比:

维度浏览器Node.js
主要宿主对象window / documentglobal / globalThis / process
默认入口HTML + <script> 标签.js 文件 + package.jsonmain
内置模块DOM、BOM、Fetch、Storagefshttpnetpathcryptostream
模块系统ESM(<script type="module">CommonJS(默认) + ESM(需 .mjspackage.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——内核和线程池处理,主线程只关心回调

一句话判断该不该用 Node

"请求量高、每次请求等待外部资源(DB / 第三方 API / 文件)多、本身的计算逻辑轻"——这种场景 Node 是首选。反过来,如果业务是图像处理、视频转码、复杂数学运算,应当把重计算交给专用服务,Node 只负责编排。

基于上面的架构特点,可以反过来判断"什么场景 Node 不擅长",这是下一节的主题。

四、适用与不适用场景

适合

  • Web API / BFF 层:Express、Koa、NestJS、Fastify 这类框架是 Node 最成熟的领域
  • 实时通信:WebSocket 服务(聊天、协作、白板)
  • 中间层 / 网关:聚合多个下游接口、做协议转换
  • CLI 工具:前端构建工具(Vite、webpack)、脚手架(create-vuenpm init
  • 轻量脚本:爬虫、定时任务、数据迁移

不擅长

  • CPU 重计算:图像处理、视频转码、科学计算、AI 推理。原因是主线程一旦被长任务占据,所有请求都得排队——CPU 密集场景的并发收益会被单线程结构吞掉
  • 强事务一致性的金融核心系统:这类业务依赖成熟的分布式事务框架、关系型数据库生态、运维工具链,Java 系在该领域积累更深;Node 生态的相关基础设施相对薄弱,生产案例少

INFO

"不擅长"不是"不能做"。Node 完全可以通过 Worker Threads、子进程或外部服务调用来扩展能力——只是相比 Go、Rust、Java 这些原生支持并发的语言,性价比更低。

五、一次完整的 Node 执行流程

bash
console.log('1. 同步执行开始');

setTimeout(() => {
  console.log('3. 宏任务回调(timers 阶段)');
}, 0);

Promise.resolve().then(() => {
  console.log('2. 微任务(promise)');
});

console.log('4. 同步执行结束');
bash
$ node hello.js
1. 同步执行开始
4. 同步执行结束
2. 微任务(promise)
3. 宏任务回调(timers 阶段)

事件循环按阶段推进,每个阶段处理一类宏任务,阶段切换前清空微任务:

每个节点的真实行为:

同步代码

JS 主线程自上而下立即执行的代码。这是整个流程唯一真正在 V8 上跑的环节。遇到 setTimeoutPromisefs.readFile 等异步调用时不会阻塞——注册回调后立即返回。

进入循环前的微任务队列

同步代码全部跑完后、事件循环进入 timers 之前,会一次性清空微任务队列。process.nextTick 的优先级高于 Promise.then——同一轮里所有 nextTick 全部跑完,才会轮到 Promise。

javascript
process.nextTick(() => console.log('nextTick'));
Promise.resolve().then(() => console.log('promise'));
// 输出:nextTick → promise

timers 阶段

执行 setTimeoutsetInterval 注册的回调。这里有两个隐藏约束:

  • 最小延迟 1ms:即使写 setTimeout(fn, 0),也不会在 0ms 时触发,会被规范强制抬到 1ms
  • 不保证精确:轮询到 timers 时只取"已经到期"的回调;如果 poll 阶段耗时较长,下一次 timers 触发会跟着推迟

pending callbacks

执行延迟到下一个循环迭代的 I/O 回调(例如某些系统级错误回调)。日常业务代码几乎碰不到,多数 Node 开发者可以忽略此阶段

idle, prepare

Node 内部使用的阶段,不对外暴露。libuv 在这里做一些空闲时的优化工作。用户代码不会在这里执行。

poll 阶段

事件循环最核心的阶段。它做两件事:

  1. 执行 poll 队列里已经到期的 I/O 回调(fs.readFilenet 的 data 事件等)
  2. 若所有阶段都没有更多回调(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