Node.js: 认识 Node.js
Alice 是一名后端开发者,她的公司需要在两周内为移动应用构建一套 REST API 后端。团队里全是前端开发者,只会 JavaScript,没人愿意再花时间学一门新语言。项目经理甚至考虑过招 Python 开发者,但时间根本来不及。就在团队陷入焦虑时,Alice 发现了 Node.js——一个让 JavaScript 跑在服务器上的运行时。前端开发者用熟悉的语法就能写后端 API,两周后项目顺利上线。这让 Alice 意识到:Node.js 不只是工具,更是打通前后端的桥梁。
你将学到:
- Node.js 的定义与核心定位
- V8 引擎与 Node.js 的底层架构
- 事件驱动和非阻塞 I/O 的工作原理
- Node.js 与浏览器 JavaScript 的关键差异
- Node.js 的版本策略与典型应用场景
1. Node.js 是什么
(1) 定义
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环境。它让 JavaScript 脱离浏览器,可以直接在服务器端运行。Node.js 不是一门新语言,也不是一个框架,而是 JavaScript 的服务器端执行平台。
(2) 诞生背景
2009 年,Ryan Dahl 基于 V8 引擎创建了 Node.js。他的初衷是解决服务器端 I/O 操作的阻塞问题——传统的 Web 服务器在处理文件读取、网络请求等 I/O 操作时会阻塞线程,而 Node.js 采用事件驱动的方式,让单线程也能高效处理大量并发连接。
▶ 示例:第一个 Node.js 脚本
创建文件 hello.js:
console.log('Hello, Node.js!');
console.log('Node.js version:', process.version);
console.log('Platform:', process.platform);
在终端运行:
node hello.js
预期输出:
Hello, Node.js!
Node.js version: v20.11.0
Platform: win32
2. V8 引擎与 Node.js 架构
(1) V8 引擎的角色
V8 是 Google 开发的 JavaScript 引擎,负责将 JavaScript 代码编译为机器码执行。Node.js 直接嵌入了 V8 引擎,因此获得了极高的执行性能。V8 的即时编译(JIT)技术让 JavaScript 的运行速度接近原生代码。
(2) Node.js 架构分层
Node.js 的架构自下而上分为四层:
| 层级 | 组成 | 作用 |
|---|---|---|
| JavaScript 代码层 | 用户编写的 .js 文件 | 业务逻辑 |
| Node.js API 层 | fs、http、path 等模块 | 提供服务器端能力 |
| Node.js Bindings 层 | C++ 桥接代码 | 连接 JavaScript 与底层 C 库 |
| 底层库层 | V8、libuv、OpenSSL 等 | 引擎与系统调用 |
(3) libuv 的关键作用
libuv 是 Node.js 的异步 I/O 库,提供了事件循环、线程池和跨平台的异步 I/O 抽象。它让 Node.js 在不同操作系统上都能以统一的方式处理文件、网络、定时器等异步操作。
▶ 示例:查看 Node.js 依赖库信息
console.log('V8 version:', process.versions.v8);
console.log('libuv version:', process.versions.libuv);
console.log('OpenSSL version:', process.versions.openssl);
console.log('Node.js version:', process.version);
输出:
V8 version: 11.3.244.8
libuv version: 1.46.0
OpenSSL version: 3.0.12
Node.js version: v20.11.0
▶ 示例:(4) 架构图
graph TB
A["JavaScript 代码<br/>(用户编写的 .js 文件)"] --> B["Node.js API<br/>(fs / http / path / ...)"]
B --> C["Node.js Bindings<br/>(C++ 桥接层)"]
C --> D1["V8 引擎<br/>(JavaScript 编译执行)"]
C --> D2["libuv<br/>(事件循环 + 异步 I/O)"]
C --> D3["其他 C 库<br/>(OpenSSL / zlib / c-ares)"]
D2 --> E["操作系统<br/>(文件 / 网络 / 进程)"]
3. 事件驱动与非阻塞 I/O
(1) 事件驱动模型
Node.js 采用事件驱动架构:程序通过注册事件回调来响应各种操作,而不是主动轮询。当 I/O 操作完成时,事件循环会将对应的回调放入队列等待执行。
(2) 非阻塞 I/O 的含义
传统服务器在读取文件时会阻塞当前线程,直到文件读取完毕。Node.js 发起 I/O 请求后立即返回,继续执行后续代码,等 I/O 完成后再通过回调处理结果。这种模式让单线程也能同时处理数百个并发请求。
(3) 事件循环简介
事件循环是 Node.js 的核心调度机制,按以下阶段循环运行:
- timers:执行 setTimeout / setInterval 回调
- pending callbacks:执行系统操作的回调
- idle, prepare:内部使用
- poll:获取新 I/O 事件,执行 I/O 回调
- check:执行 setImmediate 回调
- close callbacks:执行关闭事件回调
▶ 示例:阻塞 vs 非阻塞读取文件
阻塞方式(不推荐):
const fs = require('fs');
const data = fs.readFileSync('/etc/hosts', 'utf8');
console.log('File read (blocking):', data.length, 'bytes');
console.log('This line runs after file is read');
非阻塞方式(推荐):
const fs = require('fs');
fs.readFile('/etc/hosts', 'utf8', (err, data) => {
if (err) throw err;
console.log('File read (non-blocking):', data.length, 'bytes');
});
console.log('This line runs immediately');
输出(非阻塞方式):
This line runs immediately
File read (non-blocking): 250 bytes
注意非阻塞方式中,"This line runs immediately" 先输出,说明 readFile 没有阻塞后续代码。
4. Node.js vs 浏览器 JavaScript
(1) 核心差异
Node.js 和浏览器中的 JavaScript 使用相同的语言规范(ECMAScript),但运行环境和可用 API 完全不同。
| 对比维度 | Node.js | 浏览器 JavaScript |
|---|---|---|
| 运行环境 | 服务器 | 浏览器 |
| 模块系统 | CommonJS / ESM | ESM |
| 全局对象 | global / process | window / document |
| 文件系统 | fs 模块直接访问 | 无法直接访问 |
| 网络 I/O | http / net 模块 | fetch / XMLHttpRequest |
| DOM 操作 | 无 DOM | 有 DOM API |
| 安装包管理 | npm | 无(或通过 CDN) |
| 主要用途 | 服务器应用、CLI 工具 | 网页交互、UI 渲染 |
(2) 共同点
- 都遵循 ECMAScript 标准
- 都使用 V8 引擎(Chrome 和 Node.js)
- 都支持 Promise / async-await
- 都有事件循环机制
▶ 示例:相同的语法,不同的 API
浏览器代码:
document.getElementById('output').textContent = 'Hello from browser';
Node.js 代码:
const fs = require('fs');
fs.writeFileSync('output.txt', 'Hello from Node.js');
同样的 JavaScript 语法,但操作对象完全不同:浏览器操作 DOM,Node.js 操作文件系统。
5. Node.js 的典型应用场景
(1) REST API 服务
Node.js 非常适合构建 RESTful API。其非阻塞 I/O 模型可以高效处理大量并发请求,JSON 是 JavaScript 的原生数据格式,省去了序列化转换的步骤。
(2) CLI 工具
Webpack、Babel、ESLint、npm 本身都是用 Node.js 构建的 CLI 工具。Node.js 提供了丰富的文件系统、子进程和命令行参数处理 API。
(3) 实时应用
聊天系统、在线协作、实时通知等场景需要持久的双向连接。Node.js 的 WebSocket 支持和事件驱动架构天然契合这类需求。
(4) 微服务
Node.js 启动快、内存占用小,适合作为轻量级微服务。每个微服务可以独立部署和扩展。
(5) 爬虫与数据采集
结合 Cheerio 等库,Node.js 可以高效抓取和解析网页数据,异步 I/O 让并发请求效率极高。
▶ 示例:应用场景对比
| 场景 | 优势 | 代表项目 |
|---|---|---|
| REST API | 非阻塞 I/O + 原生 JSON | Express / Fastify / Koa |
| CLI 工具 | 文件操作 + 子进程 | Webpack / Babel / ESLint |
| 实时应用 | WebSocket + 事件驱动 | Socket.IO / ws |
| 微服务 | 轻量 + 快速启动 | NestJS / seneca |
| 爬虫 | 异步并发 + DOM 解析 | Puppeteer / Cheerio |
▶ 示例:5 行代码创建 HTTP 服务器
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ message: 'Hello from Node.js API' }));
});
server.listen(3000, () => console.log('Server running on port 3000'));
node server.js
Server running on port 3000
访问 http://localhost:3000 即可看到 JSON 响应。
6. Node.js 版本策略
(1) LTS 与 Current
Node.js 采用双轨发布策略:
| 对比项 | LTS(长期支持版) | Current(当前版) |
|---|---|---|
| 发布周期 | 每两年一个大版本 | 每 6 个月一个大版本 |
| 维护期 | 30 个月 | 约 6 个月 |
| 稳定性 | 高,仅接收 bug 修复和安全补丁 | 低,包含最新特性 |
| 适用场景 | 生产环境 | 体验新特性、测试 |
| 版本号规则 | 偶数号(18、20、22...) | 奇数号(19、21、23...) |
(2) 版本选择建议
- 生产项目:始终使用 Active LTS 版本
- 学习阶段:推荐使用 Active LTS,资料和生态最完善
- 尝鲜特性:可使用 Current 版本,但不建议用于生产
(3) 版本管理工具
推荐使用 nvm(Node Version Manager)来管理和切换 Node.js 版本。
▶ 示例:使用 nvm 管理版本
nvm install 20
nvm use 20
nvm ls
v18.17.0
-> v20.11.0
system
default -> 20 (-> v20.11.0)
▶ 示例:查看当前 Node.js 版本与状态
node -v
node -e "console.log(process.release.lts ? 'LTS: ' + process.release.lts : 'Current (non-LTS)')"
v20.11.0
LTS: Iron
❓ 常见问题
node -v 验证即可。📖 小节
- Node.js 是基于 V8 引擎的 JavaScript 服务器端运行时,不是新语言
- 架构分四层:JavaScript 代码 → Node.js API → Bindings → 底层库(V8 / libuv)
- 事件驱动 + 非阻塞 I/O 是 Node.js 高并发的核心机制
- Node.js 与浏览器 JS 语法相同但 API 和运行环境完全不同
- 典型场景:REST API、CLI 工具、实时应用、微服务、爬虫
- 生产环境务必使用 LTS 版本,用 nvm 管理多版本
📝 作业
- 安装 Node.js(推荐 LTS 版本),运行
node -v确认安装成功,记录你的版本号和是否为 LTS - 创建
hello.js文件,使用console.log输出你的名字和 Node.js 版本号,运行并截图 - 编写脚本使用
process对象输出以下信息:Node.js 版本、操作系统平台、当前工作目录、CPU 架构 - 使用
fs.readFile非阻塞方式读取一个文本文件并输出内容,观察回调函数的执行顺序 - 对比
fs.readFileSync(阻塞)和fs.readFile(非阻塞),在读取同一文件时分别在回调前后打印时间戳,说明两者的执行差异
7. 综合示例:系统信息读取脚本
以下脚本使用 Node.js 内置模块读取并输出系统信息,展示 Node.js 作为服务器端运行时的基本能力:
const os = require('os');
const fs = require('fs');
const path = require('path');
const sysInfo = {
nodeVersion: process.version,
platform: process.platform,
arch: process.arch,
hostname: os.hostname(),
totalMemoryMB: Math.round(os.totalmem() / 1024 / 1024),
freeMemoryMB: Math.round(os.freemem() / 1024 / 1024),
cpuCores: os.cpus().length,
cpuModel: os.cpus()[0].model,
uptimeHours: (os.uptime() / 3600).toFixed(2),
cwd: process.cwd(),
homedir: os.homedir()
};
const output = JSON.stringify(sysInfo, null, 2);
console.log(output);
const filePath = path.join(os.homedir(), 'nodejs-sysinfo.txt');
fs.writeFile(filePath, output, 'utf8', (err) => {
if (err) {
console.error('Write failed:', err.message);
return;
}
console.log('System info saved to:', filePath);
});
预期输出:
{
"nodeVersion": "v20.11.0",
"platform": "win32",
"arch": "x64",
"hostname": "DESKTOP-ABC123",
"totalMemoryMB": 16384,
"freeMemoryMB": 8192,
"cpuCores": 8,
"cpuModel": "Intel(R) Core(TM) i7-12700K",
"uptimeHours": "48.32",
"cwd": "C:\\projects\\node-demo",
"homedir": "C:\\Users\\Alice"
}
System info saved to: C:\Users\Alice\nodejs-sysinfo.txt
脚本展示了 Node.js 的三个核心能力:操作系统信息获取(os)、文件写入(fs)、路径处理(path),同时使用了非阻塞 I/O 写入文件——fs.writeFile 的回调在 console.log 之后执行,体现了事件驱动的特性。