基础知识
约 1670 字大约 6 分钟
nodejavascript
2024-08-13
1. Node 基础
什么是 Node.js
Node.js® 是一个基于 Chrome V8 引擎 的 JavaScript 运行时。
Node.js 特性:
- 非阻塞 I/O
- 事件驱动
- 单线程 + 事件循环(Event Loop),I/O 密集型场景高吞吐
- 一套语言打通前后端,生态依托 npm
与前端的不同:
- JS 核心语法不变(ECMAScript)
- 运行环境不同:前端是浏览器(BOM / DOM / Web API),Node 是 V8 + libuv,提供
fs/http/buffer/events/os/path/stream等原生模块 - 模块系统不同:浏览器原生 ESM,Node 历史上以 CommonJS 为主,目前同时支持 ESM
1.1 Node.js 提供的能力与原生 API
Node 的"能力"本质是 V8 之外、由 C++/libuv 暴露给 JS 的一组原生模块:
| 分类 | 模块 | 典型用途 |
|---|---|---|
| 文件系统 | fs / fs/promises | 读写文件、目录遍历、watch |
| 网络 | http / https / net / dgram | HTTP 服务、TCP/UDP |
| 流 | stream | 大文件、管道、背压 |
| 二进制 | buffer | 处理字节、编码转换 |
| 进程 | process / child_process / cluster / worker_threads | 环境变量、子进程、多核 |
| 事件 | events | EventEmitter 发布订阅 |
| 工具 | path / os / url / querystring / crypto / zlib | 路径、系统信息、加解密、压缩 |
| 调试 | inspector / perf_hooks | 调试、性能采样 |
几个常用示例:
// fs/promises:异步读取文件
import { readFile } from 'node:fs/promises'
const text = await readFile(new URL('./data.json', import.meta.url), 'utf-8')
// events:EventEmitter
import { EventEmitter } from 'node:events'
const bus = new EventEmitter()
bus.on('msg', (payload) => console.log(payload))
bus.emit('msg', { hello: 'node' })
// crypto:哈希
import { createHash } from 'node:crypto'
const hash = createHash('sha256').update('hello').digest('hex')推荐使用 node: 前缀显式引入内置模块,避免被同名第三方包覆盖。
1.2 Node 原生 Web Server 实践与单测写法
不依赖 Express/Koa,仅用 http 模块即可写出一个可测试的 Web Server:
// server.js
import { createServer } from 'node:http'
export function createApp() {
return createServer(async (req, res) => {
if (req.method === 'GET' && req.url === '/health') {
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(JSON.stringify({ ok: true }))
return
}
if (req.method === 'POST' && req.url === '/echo') {
const chunks = []
for await (const chunk of req) chunks.push(chunk)
const body = Buffer.concat(chunks).toString('utf-8')
res.writeHead(200, { 'Content-Type': 'text/plain' })
res.end(body)
return
}
res.writeHead(404)
res.end('Not Found')
})
}
// 直接运行时启动;被测试 import 时不自动监听
if (import.meta.url === `file://${process.argv[1]}`) {
createApp().listen(3000, () => console.log('listening on 3000'))
}用 Node 内置的 node:test + node:assert 写单测(无需引入 Jest):
// server.test.js
import { test } from 'node:test'
import assert from 'node:assert/strict'
import { once } from 'node:events'
import { createApp } from './server.js'
async function withServer(run) {
const server = createApp().listen(0)
await once(server, 'listening')
const { port } = server.address()
try {
await run(`http://127.0.0.1:${port}`)
} finally {
server.close()
}
}
test('GET /health 返回 200', async () => {
await withServer(async (base) => {
const res = await fetch(`${base}/health`)
assert.equal(res.status, 200)
assert.deepEqual(await res.json(), { ok: true })
})
})
test('POST /echo 回显请求体', async () => {
await withServer(async (base) => {
const res = await fetch(`${base}/echo`, { method: 'POST', body: 'hi' })
assert.equal(await res.text(), 'hi')
})
})运行:node --test。要点:
- 监听端口填
0,让系统随机分配,避免测试间端口冲突 - 通过
createApp工厂函数将"构建"和"监听"解耦,方便被测试引用 - 测试结束记得
server.close()防止进程悬挂
1.3 Node 进阶及原理解析
- 事件循环(Event Loop):由 libuv 驱动,分为 timers → pending callbacks → idle/prepare → poll → check → close callbacks 六个阶段。
setImmediate在 check 阶段执行,setTimeout在 timers 阶段执行;process.nextTick和 Promise microtask 在每个阶段切换之间清空,优先级最高。详见 Node 核心模块。 - 单线程与多线程:JS 主线程单线程执行,但底层 libuv 维护线程池(默认 4)处理文件 I/O、DNS、
crypto.pbkdf2等。CPU 密集任务要用worker_threads,而不是child_process。 - 模块机制:CommonJS 通过
Module._load→Module._resolveFilename→ 编译包装成函数(exports, require, module, __filename, __dirname) => { ... };ESM 走静态解析、异步 link。两者互操作时注意require(ESM)的限制与import()动态导入。 - Buffer 与 Stream:Buffer 在堆外分配(8KB 以上走
createUnsafe),避免 V8 GC 压力;Stream 基于背压(highWaterMark)保证内存安全,pipeline比手写pipe更安全(自动处理错误与清理)。 - GC 与内存:V8 分新生代(Scavenge)和老生代(Mark-Sweep / Mark-Compact),Node 启动参数
--max-old-space-size=4096可调老生代上限。线上排查内存泄漏常用--inspect+ Chrome DevTools 或heapdump。 - 错误处理:未捕获的 Promise rejection 在新版本 Node 默认会使进程退出,务必统一
try/catch或.catch;domain已废弃,推荐AsyncLocalStorage做请求级上下文追踪。
1.4 NVM:Node 版本管理工具
nvm 让同一台机器可以共存多个 Node 版本,按项目切换。
安装(macOS / Linux):
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash安装后在 ~/.zshrc 追加(安装脚本通常会自动写入):
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"常用命令:
nvm ls-remote --lts # 查看所有 LTS 版本
nvm install --lts # 安装最新 LTS
nvm install 20.11.1 # 安装指定版本
nvm use 20 # 本终端切换到 20.x
nvm alias default 20 # 设置默认版本
nvm current # 查看当前使用的版本
nvm uninstall 18.19.0 # 卸载项目级自动切换:在项目根目录放一个 .nvmrc:
20.11.1进入目录后执行 nvm use 即可读取该文件。配合 zsh 的 chpwd hook 还能做到 cd 时自动切换。
Windows 用户推荐 nvm-windows 或 fnm(跨平台、更快)。
2. webSocket
example:
const socket = require("socket.io");
const http = require("http");
// 创建服务
const server = http
.createServer((req, res) => {
// 允许所有跨域请求
res.setHeader("Access-Control-Allow-Origin", "*");
req.writeHead(200, {
"Content-Type": "text/html",
});
res.end("");
})
.listen(3000);
let pad = null,
pc = null,
padReady = false,
pcReady = false;
// 连接socket.io
socket.listen(server).on("connection", (conn) => {
conn.on("message", (str) => {
if (str === "Pad") {
pad = conn;
padReady = true;
conn.send("连接成功");
console.log("Pad");
}
if (str === "PC") {
pc = conn;
pcReady = true;
console.log("Pc");
}
if (padReady && pcReady) {
if (str === "PC") str = "我是PC界面";
pc.send(str);
}
});
conn.on("disconnection", (code, reason) => {
console.log("关闭连接");
});
});参考:
3. Node 实现 ZIP 压缩文件
JSZip 工具的使用
下载 JSZip
yarn add jszip官网实例
// 创建实例
var zip = new JSZip();
// 创建 Hello.txt 并传入数据
zip.file("Hello.txt", "Hello World\n");
// 创建文件夹 images
var img = zip.folder("images");
// 在img文件夹中 创建 smile.gif 并传入数据
img.file("smile.gif", imgData, {
base64: true,
});
// 进行压缩
zip
.generateAsync({
type: "blob",
})
.then(function(content) {
// 将压缩后的内容导入指定文件中
saveAs(content, "example.zip");
});简单使用
const ZIP = require("jszip");
async function createZIP(data, filename) {
// 初始化 jszip
const zip = new ZIP();
// 读取文件内容
const packageJSON = await readFile(getFilePath("./package.ejs"));
const indexJSTemplate = await readFile(getFilePath("./index.ejs"));
// index.js 模板渲染
const renderData = {
secret: data.secret,
// cmd: "cd C:\\APP\\MyWorkLoad && git pull",
cmd: `cd ${data.file} && ${data.cmd}`,
path: data.url,
port: data.port,
};
const indexJS = ejs.render(indexJSTemplate, renderData);
// 创建文件 并将数据传入文件
zip.file("package.json", packageJSON);
zip.file("index.js", indexJS);
// 进行文件压缩 content即压缩后的数据
const content = await zip.generateAsync({
type: "nodebuffer", // node压缩类型
compressionOptions: {
level: 5, // 压缩级别 1-9级
},
});
// 将压缩文件存储到静态文件中
await fs.writeFile(getFilePath(`../../static/${filename}`), content);
return `${filename}`;
}