Node.js速查手册
Node模块/fs/path/Stream/事件循环速查
📦 模块系统 CommonJS / ESM
Node 传统模块系统,动态加载、可在任意位置 require。
// math.js
function add(a, b) { return a + b }
const PI = 3.14
module.exports = { add, PI }
// 或逐个挂载
exports.sub = (a, b) => a - b
// app.js
const math = require('./math')
const { add } = require('./math')
const path = require('node:path') // 加 node: 前缀更明确
console.log(add(1, 2))
现代标准,静态分析、支持 tree-shaking;文件用 .mjs 或 package.json 声明。
// math.js
export const PI = 3.14
export function add(a, b) { return a + b }
export default { version: '1.0' }
// app.js
import math, { add, PI } from './math.js'
import * as M from './math.js'
import { readFile } from 'node:fs/promises'
console.log(add(1, 2), PI)
在 package.json 加 "type": "module";ESM 下没有 __dirname/__filename。
// package.json
{
"type": "module"
}
// ESM 中模拟 __dirname / __filename
import { fileURLToPath } from 'node:url'
import { dirname, join } from 'node:path'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
const cfg = join(__dirname, 'config.json')
返回 Promise,可按条件或运行时路径加载,适合按需加载。
async function load(kind) {
if (kind === 'json') {
const data = await import('./data.json', { with: { type: 'json' } })
return data.default
}
const mod = await import(`./plugins/${kind}.js`)
return mod.default
}
// CommonJS 中也可用于异步加载 ESM
;(async () => {
const { default: p } = await import('node:path')
})()
📁 fs 文件系统
fs/promises 是现代推荐写法,配合 await 无回调地狱。
import { readFile, readdir, stat } from 'node:fs/promises'
const text = await readFile('a.txt', 'utf8')
const buf = await readFile('a.bin') // 不传编码返回 Buffer
for (const name of await readdir('.')) {
const s = await stat(name)
console.log(s.isDirectory() ? 'D' : 'F', name, s.size)
}
// 递归列出全部文件
const all = await readdir('src', { recursive: true })
writeFile 默认覆盖;flag: 'a' 追加;mkdir recursive 递归建目录。
import { writeFile, appendFile, mkdir } from 'node:fs/promises'
await writeFile('b.txt', 'hello\n', 'utf8')
await writeFile('b.txt', JSON.stringify({ a: 1 }, null, 2))
await appendFile('log.txt', `${new Date().toISOString()} ok\n`)
await mkdir('out/logs', { recursive: true })
await writeFile('out/data/a.json', '{}', { flag: 'wx' }) // 已存在则报错
cp 支持递归复制目录(Node 16.7+);rm 递归删除。
import { stat, rename, unlink, rm, cp } from 'node:fs/promises'
const s = await stat('a.txt')
s.isFile(); s.isDirectory(); s.size; s.mtime
await rename('old.txt', 'new.txt')
await unlink('tmp.txt')
await rm('dist', { recursive: true, force: true })
await cp('assets', 'backup/assets', { recursive: true })
// 判断存在(不推荐 exists,直接 stat/catch)
try { await stat('x') } catch { /* 不存在 */ }
Sync 后缀阻塞事件循环,仅适合启动期/脚本,服务中慎用。
import { readFile } from 'node:fs'
readFile('a.txt', 'utf8', (err, data) => {
if (err) throw err
console.log(data)
})
import { readFileSync } from 'node:fs'
const cfg = JSON.parse(readFileSync('config.json', 'utf8'))
// 启动时读取配置可以接受
open 后通过 FileHandle 做精细控制;记得关闭。
import { open } from 'node:fs/promises'
const fh = await open('a.txt', 'r')
try {
const buf = Buffer.alloc(100)
const { bytesRead } = await fh.read(buf, 0, 100, 0)
console.log('读到', bytesRead, '字节')
} finally {
await fh.close()
}
// 标志位:r 只读 / r+ 读写 / w 覆盖写 / a 追加 / wx 新建排他
🛣 path 路径
join 只拼接,resolve 一定转成绝对路径。
import path from 'node:path'
path.join('src', 'img', 'a.png') // src/img/a.png
path.resolve('src', 'img/a.png') // /cwd/src/img/a.png
path.resolve('/a', '/b', 'c') // /b/c
path.normalize('a//b/../c/') // a/c/
path.relative('/a/b/c', '/a/d') // ../../d
path.sep // / 或 \
basename 文件名、dirname 目录、extname 扩展名。
const p = '/data/app/index.test.js'
path.basename(p) // 'index.test.js'
path.basename(p, '.js') // 'index.test'
path.dirname(p) // '/data/app'
path.extname(p) // '.js'
path.parse(p)
// { root: '/', dir: '/data/app', base: 'index.test.js',
// ext: '.js', name: 'index.test' }
path.format({ dir: '/x', name: 'a', ext: '.txt' })
永远用 path API 拼路径,不要手写 / 或 \;ESM 可用 import.meta.url。
// ❌ Windows 会出问题
const bad = __dirname + '/config.json'
// ✅
const good = path.join(__dirname, 'config.json')
// ESM 里配合 fileURLToPath
import { pathToFileURL } from 'node:url'
const u = pathToFileURL('/data/a.txt') // file:///data/a.txt
🌐 http 原生服务
无需框架即可创建服务;每个请求进入同一个回调。
import http from 'node:http'
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' })
res.end(JSON.stringify({ ok: true, path: req.url }))
})
server.listen(3000, () => {
console.log('listening http://127.0.0.1:3000')
})
用 method + url 自行分发;URL 解析用 WHATWG URL。
http.createServer((req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`)
if (req.method === 'GET' && url.pathname === '/ping') {
res.end('pong')
} else if (req.method === 'GET' && url.pathname === '/hello') {
res.end(`hello ${url.searchParams.get('name') ?? ''}`)
} else {
res.writeHead(404); res.end('Not Found')
}
})
req 是流,需要监听 data/end 收集;注意限制大小。
function readBody(req, limit = 1e6) {
return new Promise((resolve, reject) => {
let size = 0
const chunks = []
req.on('data', c => {
size += c.length
if (size > limit) { reject(new Error('body too large')); req.destroy() }
chunks.push(c)
})
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')))
req.on('error', reject)
})
}
// 路由内:
// const raw = await readBody(req)
// const body = JSON.parse(raw)
Node 18+ 内置全局 fetch;低版本可用 http.get 或 undici。
const res = await fetch('https://api.example.com/ping')
console.log(res.status, await res.text())
const r = await fetch('https://api.example.com/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Tom' })
})
const user = await r.json()
// 经典 API
http.get('http://example.com', (resp) => {
resp.pipe(process.stdout)
})
⚙️ process 进程
argv 前两项固定是 node 与脚本路径;env 读环境变量。
// node app.js 8080 --debug
console.log(process.argv)
// ['/usr/bin/node', '/path/app.js', '8080', '--debug']
const port = Number(process.argv[2]) || 3000
const env = process.env.NODE_ENV || 'development'
const token = process.env.API_TOKEN
process.cwd() // 当前工作目录(不一定等于脚本目录)
process.hrtime.bigint()
process.uptime()
优雅退出:先关服务、清资源再 exit;0 成功非 0 异常。
process.on('SIGTERM', async () => {
console.log('收到 SIGTERM,准备关闭')
await server.close()
process.exit(0)
})
process.on('SIGINT', () => {
console.log('Ctrl+C')
process.exit(0)
})
process.on('uncaughtException', (err) => {
console.error('未捕获异常', err)
process.exit(1)
})
process.on('unhandledRejection', (reason) => console.error(reason))
标准输入输出本身就是流;nextTick 是最高优先级微任务。
process.stdout.write('请输入:')
process.stdin.setEncoding('utf8')
process.stdin.once('data', (line) => {
console.log('你输入了', line.trim())
})
// 微任务:promise 队列之前
process.nextTick(() => console.log('tick'))
Promise.resolve().then(() => console.log('promise'))
// 输出:tick -> promise
🔁 事件循环与异步顺序
同步代码 → nextTick → Promise 微任务 → timers → poll → check(setImmediate)。
setTimeout(() => console.log('timeout'), 0)
setImmediate(() => console.log('immediate'))
Promise.resolve().then(() => console.log('promise'))
process.nextTick(() => console.log('nextTick'))
console.log('sync')
// 输出顺序:
// sync -> nextTick -> promise -> (timeout/immediate 视轮次)
I/O 回调内 setImmediate 一定先于 setTimeout;主模块中二者顺序不定。
const fs = require('node:fs')
fs.readFile(__filename, () => {
setTimeout(() => console.log('timeout'))
setImmediate(() => console.log('immediate'))
})
// I/O 回调中:immediate 总是先输出
setImmediate(() => console.log('immediate'))
setTimeout(() => console.log('timeout'))
// 主模块中:顺序不保证
老回调风格可用 node:util 的 promisify 包装。
import { promisify } from 'node:util'
import { readFile } from 'node:fs'
const read = promisify(readFile)
const txt = await read('a.txt', 'utf8')
const sleep = promisify(setTimeout)
await sleep(1000)
console.log('1 秒后')
🧊 Buffer
Buffer 是固定长度的二进制数据容器(Uint8Array 子类)。
Buffer.alloc(10) // 10 字节,初始化为 0
Buffer.alloc(5, 0xff) // 全部 0xff
Buffer.from('hello', 'utf8') // 字符串 -> Buffer
Buffer.from([104, 101, 108]) // 字节数组 -> Buffer
Buffer.from('6869', 'hex') // 十六进制 -> Buffer
Buffer.byteLength('中文') // 字节数(utf8 下为 6)
支持 base64、hex、latin1 等,常用于编解码小工具。
const b = Buffer.from('hello 世界', 'utf8')
b.toString('base64') // aGVsbG8g5LiW55WM
b.toString('hex') // 68656c6c6f...
b.toString('utf8') // hello 世界
// base64 解码
Buffer.from('aGVsbG8=', 'base64').toString('utf8') // hello
concat 合并多块(网络/流场景);slice 共享内存,复制用 subarray+copy。
const merged = Buffer.concat([Buffer.from('a'), Buffer.from('b')])
merged.length
merged[0] // 97(按字节索引)
const part = merged.subarray(0, 1)
const dst = Buffer.alloc(2)
merged.copy(dst, 0, 0, 2)
Buffer.compare(a, b) // 0 表示相等
🌊 Stream 流
读流 pipe 到写流,边读边写不占大内存。
import { createReadStream, createWriteStream } from 'node:fs'
const r = createReadStream('big.zip')
const w = createWriteStream('big.copy.zip')
r.pipe(w)
w.on('finish', () => console.log('拷贝完成'))
r.on('error', err => console.error(err))
手动监听分片;背压(write 返回 false)时 pipe 会自动处理。
const stream = createReadStream('access.log', { encoding: 'utf8', highWaterMark: 64 * 1024 })
stream.on('data', (chunk) => {
console.log('分片', chunk.length)
})
stream.on('end', () => console.log('读完'))
stream.on('error', (e) => console.error(e))
pipeline 自动传播错误并清理资源;gzip 是典型转换流。
import { pipeline } from 'node:stream/promises'
import { createGzip } from 'node:zlib'
await pipeline(
createReadStream('a.txt'),
createGzip(),
createWriteStream('a.txt.gz')
)
// 自定义 Transform
import { Transform } from 'node:stream'
const upper = new Transform({
transform(chunk, enc, cb) {
cb(null, chunk.toString().toUpperCase())
}
})
🐞 调试与其他
--watch 改代码自动重启,--inspect 用 Chrome DevTools 调试。
node app.js
node --watch app.js # Node 18+ 文件变化自动重启
node -e "console.log(process.version)"
node -r dotenv/config app.js # 启动时预加载模块
node --inspect-brk app.js # 首行断住,等待调试器连接
node --inspect=9229 app.js
# 打开 chrome://inspect 或 VS Code attach
无需 require 即可使用;部分在 ESM 下不存在(__dirname 等)。
console.log / console.error / console.time('k') / console.table(arr)
process // 当前进程
globalThis // 全局对象
setTimeout / setInterval / setImmediate
URL / URLSearchParams // WHATWG URL
TextEncoder / TextDecoder // 编解码
fetch / Request / Response // Node 18+
AbortController // 取消异步任务
queueMicrotask(() => {}) // 微任务
大量核心对象(流、server)都继承自它;on 监听、emit 触发。
import { EventEmitter } from 'node:events'
const bus = new EventEmitter()
bus.on('order', (id) => console.log('收到订单', id))
bus.once('init', () => console.log('只触发一次'))
bus.emit('order', 1001)
bus.emit('init')
bus.off('order', handler)
bus.removeAllListeners('order')
// 捕获错误事件,否则抛出会崩进程
bus.on('error', (err) => console.error(err))
😶 没有匹配的条目,换个关键词试试
📖 使用说明
全程在浏览器本地运行。
操作步骤:
- 搜索或浏览分类条目;
- 查看语法与示例代码;
- 点击复制代码块。
💬 用户评论 (0)
还没有评论,快来抢沙发!