Node.js 入门
Node.js 让 JavaScript 走出了浏览器,跑在服务器上。它复用了 V8 引擎和语言本身,又叠加了文件系统、网络、进程等"后端能力",成为现代前端工程化的基础设施——构建工具、SSR、CLI 全都建立在它之上。
一、Node.js 是什么
Node.js 是一个基于 V8 引擎 的 JavaScript 运行时。它由 Ryan Dahl 于 2009 年发布,核心设计目标是:用单线程 + 事件驱动 + 非阻塞 I/O 的模型,处理高并发的 I/O 密集场景。
| 特性 | 说明 |
|---|---|
| V8 运行时 | 浏览器同款 JS 引擎,负责把 JS 编译成机器码执行 |
| 事件驱动 | 通过事件回调响应外部请求,不等待、不阻塞 |
| 非阻塞 I/O | 发起文件/网络操作后立即返回,完成时触发回调 |
| 单线程 JS | 主线程只跑 JS 逻辑,重活交给底层线程池 |
// 传统阻塞式思维:先等文件读完,才能继续
// 非阻塞式思维:发出读取请求,注册回调,先干别的
const fs = require("fs");
fs.readFile("data.txt", "utf8", (err, data) => {
if (err) throw err;
console.log("读取完成:", data.length);
});
console.log("我先执行,文件读取在后台进行");
// 输出顺序:先打印 console.log,再打印"读取完成"1.1 事件驱动与非阻塞 I/O
Node 把耗时操作(磁盘读写、网络请求、数据库查询)交给操作系统或内部线程池,完成后通过事件队列通知主线程执行回调。所以单个 Node 进程可以同时"挂着"成千上万个连接,而不需要为每个连接开一个线程。
const http = require("http");
const server = http.createServer((req, res) => {
// 每来一个请求就触发一次回调,互不等待
res.end("hello");
});
server.listen(3000);
// 1000 个并发请求也只需要一个进程 + 一个事件循环二、Node 架构
Node.js 自底向上大致分为三层:
┌──────────────────────────────┐
│ JS 层:你的代码 + 内置模块 │
├──────────────────────────────┤
│ 绑定层:V8 / Node API 绑定 │
├──────────────────────────────┤
│ C++ 层:libuv + 其他原生库 │
└──────────────────────────────┘2.1 libuv 与事件循环
libuv 是 Node 的底层异步 I/O 库,负责:封装事件循环(Event Loop)、提供线程池(Thread Pool)、处理文件/网络/DNS 等系统调用。JS 层写的异步代码,最终都会落到 libuv 上调度。
| 组件 | 职责 |
|---|---|
| 事件循环 | 循环处理各种阶段的任务队列 |
| 线程池 | 默认 4 个线程,处理 fs、dns 等阻塞型操作 |
| 定时器 | setTimeout / setInterval 的底层实现 |
| 信号处理 | SIGINT、SIGTERM 等进程信号 |
事件循环各阶段的执行顺序会在后续《Node 事件驱动深入》中详述。
2.2 模块系统
Node 自带两种模块体系:CommonJS(默认,.js 文件)与 ES Module(.mjs 或通过 package.json 声明)。
// CommonJS 写法
const path = require("path");
module.exports = { name: "demo" };// ESM 写法
import path from "path";
export const name = "demo";三、Node 与浏览器环境差异
Node 和浏览器都运行 JS,但宿主环境完全不同:
| 对比项 | Node.js | 浏览器 |
|---|---|---|
| 全局对象 | global / globalThis | window / globalThis |
顶层 this | module.exports | window |
| DOM / BOM | 无 | 有(document、location 等) |
| 文件系统 | 有(fs) | 无 |
| 网络 | http、net | fetch、XMLHttpRequest |
| 模块 | CommonJS / ESM | ESM(<script type="module">) |
| 环境变量 | process.env | 无直接对应 |
// 在 Node 中运行:顶层 this 指向 module.exports
console.log(this === module.exports); // true
// 在浏览器中运行:顶层 this 指向 window
// console.log(this === window); // true写通用代码时,推荐用 globalThis 获取全局对象,它在两个环境都指向同一目标。
console.log(typeof globalThis); // object,两个环境都可用四、安装与版本管理
4.1 直接安装与 nvm
Windows 推荐使用 nvm-windows 管理多版本 Node:
# 安装 nvm 后
nvm install 20.11.0 # 安装指定版本
nvm install lts # 安装最新 LTS
nvm use 20.11.0 # 切换版本
nvm ls # 查看已安装版本4.2 LTS 与 Current
| 版本类型 | 说明 | 适用场景 |
|---|---|---|
| LTS(长期支持) | 偶数主版本,维护周期长 | 生产环境首选 |
| Current(当前版) | 奇数主版本,新特性先行 | 尝鲜、实验 |
node -v # 查看版本
npm -v # 查看 npm 版本五、全局对象与全局变量
Node 里不需要 require 就能直接使用的对象,分为两类:全局对象(真正的全局)与模块级变量(每个文件都注入,但并非全局)。
| 名称 | 类型 | 说明 |
|---|---|---|
global | 全局对象 | Node 的全局对象,类似浏览器的 window |
globalThis | 全局对象 | 标准化的全局对象,跨环境通用 |
process | 全局对象 | 当前进程信息与控制 |
Buffer | 全局类 | 二进制数据缓冲 |
__dirname | 模块变量 | 当前文件所在目录的绝对路径 |
__filename | 模块变量 | 当前文件的绝对路径 |
module | 模块变量 | 当前模块对象 |
exports | 模块变量 | module.exports 的引用 |
require | 模块变量 | 加载模块的函数 |
// 全局对象
console.log(global === globalThis); // true(Node 20+ 一定成立)
console.log(process.version); // 当前 Node 版本
// 模块级变量(CommonJS 文件内)
console.log(__dirname); // 例如 D:\code\demo
console.log(__filename); // 例如 D:\code\demo\app.js
console.log(module.filename === __filename); // true5.1 Buffer
Buffer 用于处理二进制数据,如文件内容、网络报文、图片流:
const buf = Buffer.from("你好", "utf8");
console.log(buf); // <Buffer e4 bd a0 e5 a5 bd>
console.log(buf.length); // 6,一个汉字占 3 字节
console.log(buf.toString("utf8")); // 你好六、模块系统:CommonJS 与 ESM
6.1 CommonJS
CommonJS 是 Node 的默认模块系统,采用同步加载,require 一个模块时该模块立即执行并导出。
// math.js
const add = (a, b) => a + b;
module.exports = { add };// app.js
const { add } = require("./math.js");
console.log(add(1, 2)); // 36.2 ESM 与 type 字段
ESM 是语言标准模块,支持静态分析、顶层 await、命名导入。在 package.json 中通过 type 字段声明模块类型:
{
"name": "my-app",
"type": "module"
}type 字段 | .js 文件的解析方式 | 备注 |
|---|---|---|
| 缺省 | CommonJS | Node 的默认行为 |
"module" | ESM | 可用 import / export |
"commonjs" | CommonJS | 显式声明 |
// package.json 中 "type": "module" 后,app.js 可直接写:
import { add } from "./math.js";
console.log(add(1, 2));6.3 CommonJS 与 ESM 对比
| 对比项 | CommonJS | ESM |
|---|---|---|
| 加载时机 | 运行时同步加载 | 解析阶段静态分析(支持动态 import()) |
| 关键字 | require / module.exports | import / export |
顶层 this | module.exports | undefined |
| 循环依赖 | 可能拿到不完整的导出 | 通过 live binding 处理 |
| 是否支持 Tree Shaking | 否 | 是 |
七、命令行运行与 REPL
7.1 运行脚本
node app.js # 运行脚本文件
node app.js --port 3000 # 带参数运行
node --watch app.js # Node 18+ 文件变更自动重启7.2 REPL
直接输入 node 进入交互式环境(Read-Eval-Print Loop),适合验证小段代码:
$ node
> 1 + 2
3
> const x = [1, 2, 3].map(n => n * 2)
undefined
> x
[ 2, 4, 6 ]
> .exit八、npm 脚本
package.json 的 scripts 字段可以定义常用命令:
{
"scripts": {
"dev": "node server.js",
"start": "node server.js",
"test": "node --test"
}
}npm run dev # 运行自定义脚本
npm start # start 与 test 可省略 run
npm testnpm run 会把 node_modules/.bin 加入 PATH,因此脚本里可以直接使用 vite、eslint 等局部安装的命令,无需全局安装。
九、process 进程管理基础
process 是 Node 中最常用的全局对象,掌管进程本身:
console.log(process.pid); // 进程 ID
console.log(process.version); // Node 版本号
console.log(process.platform); // 操作系统平台,如 win32
console.log(process.cwd()); // 当前工作目录
console.log(process.argv); // 命令行参数数组
console.log(process.env.NODE_ENV); // 环境变量,未设置则为 undefined
console.log(process.memoryUsage()); // 内存使用情况node app.js hello
# process.argv 输出类似:
# [ 'D:\\node\\node.exe', 'D:\\code\\demo\\app.js', 'hello' ]
# argv[0] 是 Node 可执行文件,argv[1] 是脚本路径,argv[2] 起才是真正的参数9.1 常用属性速查
| 属性/方法 | 作用 |
|---|---|
process.exit(code) | 立即退出进程,code 0 表示正常 |
process.exitCode | 设置退出码但让进程自然结束 |
process.on("exit", fn) | 进程退出前同步执行 |
process.env | 环境变量对象 |
process.cwd() | 当前工作目录 |
process.nextTick(fn) | 把回调放到当前事件循环的下一轮微任务 |
process.exitCode = 0; // 正常退出
// 有异步操作在跑时,尽量用 exitCode 而非强制 exit(),
// 强制 exit() 会直接中断未完成的异步任务掌握运行模型、环境差异、模块系统与进程基础之后,就可以动手写真实的 Node 程序了。下一站是《文件系统与路径》,从最常用的 fs 与 path 模块开始。