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

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

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

一、Node.js 是什么

Node.js 是基于 V8 引擎 + libuv + 一组内置核心模块(fshttpnetcrypto 等)的 JavaScript 运行时,主要用于在操作系统层执行 JavaScript 代码。
#mermaid-svg-3RsTC0ARr5ANf0rM{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-3RsTC0ARr5ANf0rM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3RsTC0ARr5ANf0rM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3RsTC0ARr5ANf0rM .error-icon{fill:#552222;}#mermaid-svg-3RsTC0ARr5ANf0rM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3RsTC0ARr5ANf0rM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3RsTC0ARr5ANf0rM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3RsTC0ARr5ANf0rM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3RsTC0ARr5ANf0rM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3RsTC0ARr5ANf0rM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3RsTC0ARr5ANf0rM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3RsTC0ARr5ANf0rM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3RsTC0ARr5ANf0rM .marker.cross{stroke:#333333;}#mermaid-svg-3RsTC0ARr5ANf0rM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3RsTC0ARr5ANf0rM p{margin:0;}#mermaid-svg-3RsTC0ARr5ANf0rM .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-3RsTC0ARr5ANf0rM .cluster-label text{fill:#333;}#mermaid-svg-3RsTC0ARr5ANf0rM .cluster-label span{color:#333;}#mermaid-svg-3RsTC0ARr5ANf0rM .cluster-label span p{background-color:transparent;}#mermaid-svg-3RsTC0ARr5ANf0rM .label text,#mermaid-svg-3RsTC0ARr5ANf0rM span{fill:#333;color:#333;}#mermaid-svg-3RsTC0ARr5ANf0rM .node rect,#mermaid-svg-3RsTC0ARr5ANf0rM .node circle,#mermaid-svg-3RsTC0ARr5ANf0rM .node ellipse,#mermaid-svg-3RsTC0ARr5ANf0rM .node polygon,#mermaid-svg-3RsTC0ARr5ANf0rM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3RsTC0ARr5ANf0rM .rough-node .label text,#mermaid-svg-3RsTC0ARr5ANf0rM .node .label text,#mermaid-svg-3RsTC0ARr5ANf0rM .image-shape .label,#mermaid-svg-3RsTC0ARr5ANf0rM .icon-shape .label{text-anchor:middle;}#mermaid-svg-3RsTC0ARr5ANf0rM .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-3RsTC0ARr5ANf0rM .rough-node .label,#mermaid-svg-3RsTC0ARr5ANf0rM .node .label,#mermaid-svg-3RsTC0ARr5ANf0rM .image-shape .label,#mermaid-svg-3RsTC0ARr5ANf0rM .icon-shape .label{text-align:center;}#mermaid-svg-3RsTC0ARr5ANf0rM .node.clickable{cursor:pointer;}#mermaid-svg-3RsTC0ARr5ANf0rM .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-3RsTC0ARr5ANf0rM .arrowheadPath{fill:#333333;}#mermaid-svg-3RsTC0ARr5ANf0rM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-3RsTC0ARr5ANf0rM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-3RsTC0ARr5ANf0rM .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3RsTC0ARr5ANf0rM .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-3RsTC0ARr5ANf0rM .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3RsTC0ARr5ANf0rM .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-3RsTC0ARr5ANf0rM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-3RsTC0ARr5ANf0rM .cluster text{fill:#333;}#mermaid-svg-3RsTC0ARr5ANf0rM .cluster span{color:#333;}#mermaid-svg-3RsTC0ARr5ANf0rM div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-3RsTC0ARr5ANf0rM .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3RsTC0ARr5ANf0rM rect.text{fill:none;stroke-width:0;}#mermaid-svg-3RsTC0ARr5ANf0rM .icon-shape,#mermaid-svg-3RsTC0ARr5ANf0rM .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3RsTC0ARr5ANf0rM .icon-shape p,#mermaid-svg-3RsTC0ARr5ANf0rM .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-3RsTC0ARr5ANf0rM .icon-shape .label rect,#mermaid-svg-3RsTC0ARr5ANf0rM .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3RsTC0ARr5ANf0rM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3RsTC0ARr5ANf0rM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3RsTC0ARr5ANf0rM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Node.js 运行时
应用代码(JavaScript)
业务逻辑
第三方包(npm)
内置模块 require
V8 引擎

编译 & 执行 JS
libuv

事件循环 / 异步 IO / 线程池
内置 C++ 绑定

fs / net / crypto ...
操作系统 / 内核

组件 角色
V8 Google 的 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 / document global / globalThis / process
默认入口 HTML + <script> 标签 .js 文件 + package.jsonmain
内置模块 DOM、BOM、Fetch、Storage fshttpnetpathcryptostream
模块系统 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 操作全部异步 + 事件循环调度
文件系统 libuv 线程池 主线程(JS) 文件系统 libuv 线程池 主线程(JS) #mermaid-svg-DuHWY4WXS9MUCYaG{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-DuHWY4WXS9MUCYaG .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-DuHWY4WXS9MUCYaG .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-DuHWY4WXS9MUCYaG .error-icon{fill:#552222;}#mermaid-svg-DuHWY4WXS9MUCYaG .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-DuHWY4WXS9MUCYaG .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-DuHWY4WXS9MUCYaG .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-DuHWY4WXS9MUCYaG .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-DuHWY4WXS9MUCYaG .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-DuHWY4WXS9MUCYaG .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-DuHWY4WXS9MUCYaG .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-DuHWY4WXS9MUCYaG .marker{fill:#333333;stroke:#333333;}#mermaid-svg-DuHWY4WXS9MUCYaG .marker.cross{stroke:#333333;}#mermaid-svg-DuHWY4WXS9MUCYaG svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-DuHWY4WXS9MUCYaG p{margin:0;}#mermaid-svg-DuHWY4WXS9MUCYaG .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-DuHWY4WXS9MUCYaG text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-DuHWY4WXS9MUCYaG .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-DuHWY4WXS9MUCYaG .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-DuHWY4WXS9MUCYaG .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-DuHWY4WXS9MUCYaG .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-DuHWY4WXS9MUCYaG #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-DuHWY4WXS9MUCYaG .sequenceNumber{fill:white;}#mermaid-svg-DuHWY4WXS9MUCYaG #sequencenumber{fill:#333;}#mermaid-svg-DuHWY4WXS9MUCYaG #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-DuHWY4WXS9MUCYaG .messageText{fill:#333;stroke:none;}#mermaid-svg-DuHWY4WXS9MUCYaG .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-DuHWY4WXS9MUCYaG .labelText,#mermaid-svg-DuHWY4WXS9MUCYaG .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-DuHWY4WXS9MUCYaG .loopText,#mermaid-svg-DuHWY4WXS9MUCYaG .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-DuHWY4WXS9MUCYaG .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-DuHWY4WXS9MUCYaG .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-DuHWY4WXS9MUCYaG .noteText,#mermaid-svg-DuHWY4WXS9MUCYaG .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-DuHWY4WXS9MUCYaG .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-DuHWY4WXS9MUCYaG .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-DuHWY4WXS9MUCYaG .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-DuHWY4WXS9MUCYaG .actorPopupMenu{position:absolute;}#mermaid-svg-DuHWY4WXS9MUCYaG .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-DuHWY4WXS9MUCYaG .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-DuHWY4WXS9MUCYaG .actor-man circle,#mermaid-svg-DuHWY4WXS9MUCYaG line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-DuHWY4WXS9MUCYaG :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 主线程立即继续执行 fs.readFile('a.txt', cb)fs.readFile('b.txt', cb)读 a.txt读 b.txta.txt 内容触发 a 的回调b.txt 内容触发 b 的回调

关键结论:

  • CPU 密集任务 (同步循环、加密、压缩、JSON 序列化大对象)会阻塞整个事件循环,导致所有请求排队等待
  • IO 密集任务(文件读写、网络请求、数据库查询)天然适合 Node------内核和线程池处理,主线程只关心回调

::: tip 一句话判断该不该用 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:hello.js 复制代码
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 阶段)

事件循环按阶段推进,每个阶段处理一类宏任务,阶段切换前清空微任务:
#mermaid-svg-GiA8S3FTI6iWTMt4{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-GiA8S3FTI6iWTMt4 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-GiA8S3FTI6iWTMt4 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-GiA8S3FTI6iWTMt4 .error-icon{fill:#552222;}#mermaid-svg-GiA8S3FTI6iWTMt4 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-GiA8S3FTI6iWTMt4 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-GiA8S3FTI6iWTMt4 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-GiA8S3FTI6iWTMt4 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-GiA8S3FTI6iWTMt4 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-GiA8S3FTI6iWTMt4 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-GiA8S3FTI6iWTMt4 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-GiA8S3FTI6iWTMt4 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-GiA8S3FTI6iWTMt4 .marker.cross{stroke:#333333;}#mermaid-svg-GiA8S3FTI6iWTMt4 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-GiA8S3FTI6iWTMt4 p{margin:0;}#mermaid-svg-GiA8S3FTI6iWTMt4 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-GiA8S3FTI6iWTMt4 .cluster-label text{fill:#333;}#mermaid-svg-GiA8S3FTI6iWTMt4 .cluster-label span{color:#333;}#mermaid-svg-GiA8S3FTI6iWTMt4 .cluster-label span p{background-color:transparent;}#mermaid-svg-GiA8S3FTI6iWTMt4 .label text,#mermaid-svg-GiA8S3FTI6iWTMt4 span{fill:#333;color:#333;}#mermaid-svg-GiA8S3FTI6iWTMt4 .node rect,#mermaid-svg-GiA8S3FTI6iWTMt4 .node circle,#mermaid-svg-GiA8S3FTI6iWTMt4 .node ellipse,#mermaid-svg-GiA8S3FTI6iWTMt4 .node polygon,#mermaid-svg-GiA8S3FTI6iWTMt4 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-GiA8S3FTI6iWTMt4 .rough-node .label text,#mermaid-svg-GiA8S3FTI6iWTMt4 .node .label text,#mermaid-svg-GiA8S3FTI6iWTMt4 .image-shape .label,#mermaid-svg-GiA8S3FTI6iWTMt4 .icon-shape .label{text-anchor:middle;}#mermaid-svg-GiA8S3FTI6iWTMt4 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-GiA8S3FTI6iWTMt4 .rough-node .label,#mermaid-svg-GiA8S3FTI6iWTMt4 .node .label,#mermaid-svg-GiA8S3FTI6iWTMt4 .image-shape .label,#mermaid-svg-GiA8S3FTI6iWTMt4 .icon-shape .label{text-align:center;}#mermaid-svg-GiA8S3FTI6iWTMt4 .node.clickable{cursor:pointer;}#mermaid-svg-GiA8S3FTI6iWTMt4 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-GiA8S3FTI6iWTMt4 .arrowheadPath{fill:#333333;}#mermaid-svg-GiA8S3FTI6iWTMt4 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-GiA8S3FTI6iWTMt4 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-GiA8S3FTI6iWTMt4 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GiA8S3FTI6iWTMt4 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-GiA8S3FTI6iWTMt4 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GiA8S3FTI6iWTMt4 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-GiA8S3FTI6iWTMt4 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-GiA8S3FTI6iWTMt4 .cluster text{fill:#333;}#mermaid-svg-GiA8S3FTI6iWTMt4 .cluster span{color:#333;}#mermaid-svg-GiA8S3FTI6iWTMt4 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-GiA8S3FTI6iWTMt4 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-GiA8S3FTI6iWTMt4 rect.text{fill:none;stroke-width:0;}#mermaid-svg-GiA8S3FTI6iWTMt4 .icon-shape,#mermaid-svg-GiA8S3FTI6iWTMt4 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GiA8S3FTI6iWTMt4 .icon-shape p,#mermaid-svg-GiA8S3FTI6iWTMt4 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-GiA8S3FTI6iWTMt4 .icon-shape .label rect,#mermaid-svg-GiA8S3FTI6iWTMt4 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GiA8S3FTI6iWTMt4 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-GiA8S3FTI6iWTMt4 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-GiA8S3FTI6iWTMt4 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是

同步代码
清空微任务队列

nextTick + Promise.then
timers 阶段

setTimeout / setInterval
pending callbacks
idle, prepare
poll 阶段

I/O 回调 / 阻塞等待新 I/O
check 阶段

setImmediate
close callbacks
清空微任务队列
还有任务?
进程退出

每个节点的真实行为:

同步代码

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
相关推荐
星火10242 小时前
【LangChain4j系列06】ChatMemory 对话记忆管理
人工智能·后端
netCode2 小时前
Git一文带你理解如何使用
后端
烂蜻蜓2 小时前
Node.js入门教程(二十四):常用工具(util 模块)
node.js
睡觉时不困4422 小时前
12.LangChain 1.0+ 第二篇:Prompt、结构化输出与 LCEL 组件组合.发布清单
后端
SomeB1oody3 小时前
【RustyML入门】2.13. 孤立森林
开发语言·后端·机器学习·rust·教程
oushaojun23 小时前
使用docker安装node.js和deepseek harness
node.js·dsh
苏三说技术3 小时前
为什么越来越多人用 Pi ? - 苏三说技术
后端
YHL4 小时前
🚀 SSE 服务器发送事件与 BFF 层实战
前端·后端
爱丶不疚4 小时前
搞不清 CommonJS 与 ESM,你是否也有这些疑问🤔
前端·node.js