Node.js 核心模块详解
Node.js 提供了丰富的内置核心模块,这些模块是开发 Node.js 应用的基础。本章将详细介绍最常用的核心模块,包括文件系统、路径处理、操作系统、URL 处理、事件驱动和流操作等。
一、文件系统模块 fs
fs 模块是 Node.js 中最重要的核心模块之一,用于与操作系统的文件系统进行交互。它提供了同步和异步两种 API 风格。
1.1 读取文件 readFile / readFileSync
readFile 是异步读取文件,readFileSync 是同步读取文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const fs = require('fs'); const path = require('path');
fs.readFile(path.join(__dirname, 'test.txt'), 'utf8', (err, data) => { if (err) { console.error('读取文件失败:', err); return; } console.log('异步读取结果:', data); });
try { const data = fs.readFileSync(path.join(__dirname, 'test.txt'), 'utf8'); console.log('同步读取结果:', data); } catch (err) { console.error('读取文件失败:', err); }
|
注意: 在实际开发中,建议优先使用异步 API,避免阻塞主线程。只有在确定不会造成性能问题的场景下才使用同步 API。
1.2 写入文件 writeFile / writeFileSync
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const fs = require('fs'); const path = require('path');
const content = '这是一段写入文件的内容';
fs.writeFile(path.join(__dirname, 'output.txt'), content, 'utf8', (err) => { if (err) { console.error('写入文件失败:', err); return; } console.log('异步写入成功'); });
try { fs.writeFileSync(path.join(__dirname, 'output-sync.txt'), content, 'utf8'); console.log('同步写入成功'); } catch (err) { console.error('写入文件失败:', err); }
|
writeFile 会覆盖文件内容。如果需要在文件末尾追加内容,可以使用 appendFile 或 appendFileSync:
1 2 3 4
| fs.appendFile(path.join(__dirname, 'output.txt'), '\n追加的内容', 'utf8', (err) => { if (err) throw err; console.log('追加写入成功'); });
|
1.3 创建和删除目录 mkdir / rmdir
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| const fs = require('fs'); const path = require('path');
fs.mkdir(path.join(__dirname, 'new-dir'), (err) => { if (err) throw err; console.log('目录创建成功'); });
fs.mkdir(path.join(__dirname, 'a', 'b', 'c'), { recursive: true }, (err) => { if (err) throw err; console.log('多级目录创建成功'); });
fs.rmdir(path.join(__dirname, 'new-dir'), (err) => { if (err) throw err; console.log('目录删除成功'); });
fs.rm(path.join(__dirname, 'a'), { recursive: true }, (err) => { if (err) throw err; console.log('多级目录删除成功'); });
|
1.4 读取目录 readdir
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const fs = require('fs'); const path = require('path');
fs.readdir(__dirname, (err, files) => { if (err) throw err; console.log('目录内容:', files); });
fs.readdir(__dirname, { withFileTypes: true }, (err, dirents) => { if (err) throw err; dirents.forEach(dirent => { if (dirent.isFile()) { console.log(`文件: ${dirent.name}`); } else if (dirent.isDirectory()) { console.log(`目录: ${dirent.name}/`); } }); });
|
1.5 流式文件操作 createReadStream / createWriteStream
当处理大文件时,readFile 和 writeFile 会将整个文件加载到内存中,可能导致内存溢出。此时应使用流(Stream)来处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| const fs = require('fs'); const path = require('path');
const readStream = fs.createReadStream(path.join(__dirname, 'large-file.txt'), { encoding: 'utf8', highWaterMark: 64 * 1024 });
const writeStream = fs.createWriteStream(path.join(__dirname, 'copy-file.txt'));
readStream.pipe(writeStream);
readStream.on('data', (chunk) => { console.log(`读取了 ${chunk.length} 字节的数据`); });
readStream.on('end', () => { console.log('文件读取完成'); });
readStream.on('error', (err) => { console.error('读取错误:', err); });
writeStream.on('finish', () => { console.log('文件写入完成'); });
|
1.6 文件监听 watch / watchFile
watch 用于监听文件或目录的变化,当文件发生改变时触发回调。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| const fs = require('fs'); const path = require('path');
fs.watch(path.join(__dirname, 'test.txt'), (eventType, filename) => { console.log(`事件类型: ${eventType}`); console.log('变化的文件:', filename); });
fs.watch(__dirname, { recursive: true }, (eventType, filename) => { if (filename) { console.log(`目录变化 - 事件: ${eventType}, 文件: ${filename}`); } });
fs.watchFile(path.join(__dirname, 'test.txt'), { interval: 1000 }, (curr, prev) => { console.log(`文件修改时间: ${new Date(curr.mtime)}`); console.log(`之前的修改时间: ${new Date(prev.mtime)}`); });
fs.unwatchFile(path.join(__dirname, 'test.txt'));
|
提示: fs.watch 在不同操作系统上的行为可能不一致。生产环境建议使用 chokidar 等第三方库,它对跨平台兼容性做了更好的封装。
二、路径模块 path
path 模块提供了处理文件路径的工具函数,不同操作系统的路径分隔符不同(Windows 用 \,Linux/Mac 用 /),path 模块可以帮我们正确处理路径。
2.1 path.join() - 拼接路径
path.join() 会将多个路径片段拼接成一个完整的路径,并自动处理路径分隔符。
1 2 3 4 5 6 7 8 9 10 11 12 13
| const path = require('path');
console.log(path.join('/usr', 'local', 'bin'));
console.log(path.join('/usr/local', '../bin', 'tool'));
console.log(path.join('', '/usr', 'local'));
|
2.2 path.resolve() - 解析绝对路径
path.resolve() 会将路径解析为绝对路径,它会从右向左依次处理路径片段,直到构造出一个绝对路径。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const path = require('path');
console.log(path.resolve('test.txt'));
console.log(path.resolve('/foo', 'bar', 'baz'));
console.log(path.resolve('/foo', '/bar', 'baz'));
console.log(path.resolve(__dirname, 'public', 'index.html'));
|
2.3 path.basename() - 获取文件名
basename() 返回路径的最后一部分,即文件名。
1 2 3 4 5 6 7 8 9 10
| const path = require('path');
console.log(path.basename('/home/user/file.txt'));
console.log(path.basename('/home/user/file.txt', '.txt'));
console.log(path.basename('/home/user/'));
|
2.4 path.dirname() - 获取目录名
dirname() 返回路径的目录部分。
1 2 3 4 5 6 7 8 9 10
| const path = require('path');
console.log(path.dirname('/home/user/file.txt'));
console.log(path.dirname('/home/user'));
console.log(path.dirname('/'));
|
2.5 path.extname() - 获取文件扩展名
extname() 返回文件的扩展名(包含点号),如果没有扩展名则返回空字符串。
1 2 3 4 5 6 7 8 9 10
| const path = require('path');
console.log(path.extname('file.txt'));
console.log(path.extname('file.tar.gz'));
console.log(path.extname('file'));
|
parse() 将路径字符串解析为对象,format() 将路径对象转换为字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| const path = require('path');
const pathObj = path.parse('/home/user/dir/file.txt'); console.log(pathObj);
const newPath = path.format({ root: '/', dir: '/home/user/dir', base: 'file.txt', ext: '.txt', name: 'file' }); console.log(newPath);
|
2.7 __dirname 和 __filename
__dirname 表示当前模块文件所在目录的绝对路径,__filename 表示当前模块文件的绝对路径。
1 2 3 4 5 6 7 8 9 10 11 12
|
console.log(__dirname);
console.log(__filename);
const configPath = path.join(__dirname, 'config', 'config.json'); console.log(configPath);
|
注意: __dirname 和 __filename 不是全局变量,它们只在模块内有效。如果想获取当前执行文件的路径,可以使用 process.argv[1] 或 require.main.filename。
三、操作系统模块 os
os 模块提供与操作系统相关的信息,包括 CPU、内存、网络接口、系统目录等。
1
| const os = require('os');
|
1 2
| console.log(os.platform());
|
3.2 os.cpus() - CPU 信息
返回一个对象数组,每个对象包含一个 CPU 核心的信息。
1 2 3 4 5 6 7 8 9 10 11 12 13
| const cpus = os.cpus();
console.log(`CPU 核心数: ${cpus.length}`); console.log(`CPU 型号: ${cpus[0].model}`); console.log(`CPU 速度: ${cpus[0].speed} MHz`);
cpus.forEach((cpu, index) => { const times = cpu.times; const total = times.user + times.nice + times.sys + times.idle + times.irq; const usage = ((times.user + times.sys) / total * 100).toFixed(2); console.log(`CPU 核心 ${index} 使用率: ${usage}%`); });
|
3.3 os.totalmem() / os.freemem() - 内存信息
1 2 3 4
| console.log(`总内存: ${(os.totalmem() / 1024 / 1024 / 1024).toFixed(2)} GB`); console.log(`空闲内存: ${(os.freemem() / 1024 / 1024 / 1024).toFixed(2)} GB`); console.log(`已用内存: ${((os.totalmem() - os.freemem()) / 1024 / 1024 / 1024).toFixed(2)} GB`); console.log(`内存使用率: ${((os.totalmem() - os.freemem()) / os.totalmem() * 100).toFixed(2)}%`);
|
3.4 os.hostname() - 主机名
1
| console.log(`主机名: ${os.hostname()}`);
|
3.5 os.tmpdir() - 临时目录
返回操作系统的默认临时文件目录。
1 2 3
| console.log(`临时目录: ${os.tmpdir()}`);
|
实际应用中常用于存储临时文件:
1 2 3 4
| const fs = require('fs'); const tempFile = path.join(os.tmpdir(), `temp-${Date.now()}.txt`); fs.writeFileSync(tempFile, '临时数据'); console.log(`临时文件已创建: ${tempFile}`);
|
3.6 os.networkInterfaces() - 网络接口
返回网络接口信息的对象,包括 IP 地址、MAC 地址等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| const interfaces = os.networkInterfaces();
Object.keys(interfaces).forEach(name => { console.log(`\n接口: ${name}`); interfaces[name].forEach(iface => { console.log(` - 类型: ${iface.family}`); console.log(` 地址: ${iface.address}`); console.log(` 子网掩码: ${iface.netmask}`); console.log(` MAC: ${iface.mac}`); console.log(` 内部: ${iface.internal}`); }); });
function getLocalIP() { const interfaces = os.networkInterfaces(); for (const name of Object.keys(interfaces)) { for (const iface of interfaces[name]) { if (iface.family === 'IPv4' && !iface.internal) { return iface.address; } } } return '127.0.0.1'; }
console.log(`本机 IP: ${getLocalIP()}`);
|
四、URL 模块 url
url 模块用于解析和处理 URL 字符串。
4.1 URL 类(WHATWG 标准)
Node.js 推荐使用 WHATWG 标准的 URL 类,它与浏览器的 URL API 保持一致。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const { URL } = require('url');
const url = new URL('https://user:pass@example.com:8080/path/to/page?query=value#section');
console.log('href:', url.href); console.log('protocol:', url.protocol); console.log('hostname:', url.hostname); console.log('port:', url.port); console.log('pathname:', url.pathname); console.log('search:', url.search); console.log('hash:', url.hash); console.log('origin:', url.origin); console.log('username:', url.username); console.log('password:', url.password);
console.log('query value:', url.searchParams.get('query')); url.searchParams.set('query', 'newvalue'); url.searchParams.append('foo', 'bar'); console.log('更新后的 URL:', url.href);
|
4.2 url.parse()(旧版 API)
url.parse() 是旧版 API,用于将 URL 字符串解析为对象。新代码建议使用 URL 类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const url = require('url');
const parsed = url.parse('https://example.com:8080/path?query=1&name=test#section', true);
console.log(parsed);
|
将 URL 对象格式化为 URL 字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const url = require('url');
const urlString = url.format({ protocol: 'https', hostname: 'example.com', port: 443, pathname: '/api/users', query: { page: '1', limit: '10' }, hash: 'list' }); console.log(urlString);
const newUrl = new URL('https://example.com'); newUrl.pathname = '/api/users'; newUrl.searchParams.set('page', '1'); newUrl.searchParams.set('limit', '10'); console.log(newUrl.toString());
|
4.4 url.resolve() - URL 拼接
用于将相对 URL 拼接成绝对 URL,类似于浏览器中 <a> 标签的解析行为。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const url = require('url');
console.log(url.resolve('https://example.com/', 'api/users'));
console.log(url.resolve('https://example.com/api/', 'users'));
console.log(url.resolve('https://example.com/api/users', 'list'));
console.log(url.resolve('https://example.com/api/users', '/list'));
console.log(url.resolve('https://example.com/api/users', '../login'));
|
4.5 querystring 处理
querystring 模块用于解析和格式化 URL 查询字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const querystring = require('querystring');
const query = querystring.parse('name=Tom&age=25&hobby=code&hobby=music'); console.log(query);
const str = querystring.stringify({ name: 'Tom', age: '25', city: 'Beijing' }); console.log(str);
const str2 = querystring.stringify({ foo: 'bar', baz: 'qux' }, ';', '='); console.log(str2);
console.log(querystring.escape('hello world 你好'));
console.log(querystring.unescape('hello%20world'));
|
提示: querystring 模块是旧版 API。对于新的项目,推荐使用 URLSearchParams(可通过 new URL('http://example.com').searchParams 访问),它提供了更现代的 API。
五、事件模块 events
Node.js 的事件系统基于 events 模块实现,许多核心模块都继承自 EventEmitter 类。
5.1 EventEmitter 基础
1 2 3 4 5 6
| const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
|
5.2 on() / emit() - 监听与触发事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| const EventEmitter = require('events');
const emitter = new EventEmitter();
emitter.on('greet', (name) => { console.log(`你好, ${name}!`); });
emitter.on('greet', (name) => { console.log(`欢迎 ${name} 来到 Node.js 世界!`); });
emitter.emit('greet', '小明');
emitter.on('data', (id, type, payload) => { console.log(`数据: id=${id}, type=${type}`, payload); });
emitter.emit('data', 1, 'json', { name: 'test', value: 123 });
|
5.3 once() - 一次性监听器
once() 注册的监听器只会被触发一次,之后自动移除。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| const EventEmitter = require('events');
const emitter = new EventEmitter();
emitter.once('start', () => { console.log('服务启动(只触发一次)'); });
emitter.emit('start'); emitter.emit('start');
let initialized = false; emitter.once('init', () => { initialized = true; console.log('初始化完成'); });
function doInit() { if (!initialized) { emitter.emit('init'); } }
doInit(); doInit();
|
5.4 removeListener() / off() - 移除监听器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| const EventEmitter = require('events');
const emitter = new EventEmitter();
function handleConnection(params) { console.log('新的连接:', params); }
emitter.on('connection', handleConnection);
emitter.emit('connection', { id: 1, ip: '192.168.1.1' });
emitter.removeListener('connection', handleConnection);
emitter.emit('connection', { id: 2, ip: '192.168.1.2' });
emitter.removeAllListeners('connection');
emitter.removeAllListeners();
|
5.5 事件继承与实战
通过继承 EventEmitter,可以创建具有事件驱动能力的自定义类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| const EventEmitter = require('events');
class TaskManager extends EventEmitter { constructor() { super(); this.tasks = []; }
addTask(name) { const task = { id: Date.now(), name, status: 'pending' }; this.tasks.push(task); this.emit('add', task); return task; }
completeTask(id) { const task = this.tasks.find(t => t.id === id); if (task) { task.status = 'completed'; this.emit('complete', task); this.emit('change', this.tasks); } }
getTasks() { return this.tasks; } }
const manager = new TaskManager();
manager.on('add', (task) => { console.log(`任务已添加: ${task.name} (ID: ${task.id})`); });
manager.on('complete', (task) => { console.log(`任务已完成: ${task.name}`); });
manager.on('change', (tasks) => { console.log(`当前任务数: ${tasks.length}`); });
manager.addTask('学习 Node.js'); manager.addTask('完成项目部署'); manager.completeTask(manager.getTasks()[0].id);
|
六、流模块 stream
流(Stream)是 Node.js 中处理流式数据的抽象接口。流可以高效地处理大数据集或逐步处理数据,而不需要一次性将全部数据加载到内存中。
6.1 流的四种类型
- 可读流(Readable):数据从源头被读取,如
fs.createReadStream、http.IncomingMessage
- 可写流(Writable):数据被写入目的地,如
fs.createWriteStream、http.ServerResponse
- 双工流(Duplex):既可读又可写,如
net.Socket
- 转换流(Transform):在读写过程中对数据进行转换,如
zlib.createGzip
6.2 可读流 Readable
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| const fs = require('fs');
const readStream = fs.createReadStream('./large-file.txt', { encoding: 'utf8', highWaterMark: 64 * 1024 });
readStream.on('open', () => { console.log('文件已打开'); });
readStream.on('data', (chunk) => { console.log(`读取数据块,大小: ${chunk.length} 字节`); });
readStream.on('end', () => { console.log('读取完成'); });
readStream.on('error', (err) => { console.error('读取错误:', err); });
readStream.on('readable', () => { let chunk; while ((chunk = readStream.read()) !== null) { console.log('手动读取:', chunk); } });
|
6.3 可写流 Writable
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| const fs = require('fs');
const writeStream = fs.createWriteStream('./output.txt');
writeStream.write('第一段数据\n'); writeStream.write('第二段数据\n'); writeStream.write('第三段数据\n');
writeStream.end('最后一段数据');
writeStream.on('finish', () => { console.log('所有数据已写入完成'); });
writeStream.on('error', (err) => { console.error('写入错误:', err); });
function writeData(data) { return new Promise((resolve, reject) => { const canWrite = writeStream.write(data); if (canWrite) { resolve(); } else { writeStream.once('drain', resolve); writeStream.once('error', reject); } }); }
|
6.4 pipe 管道
pipe() 是流最重要的功能之一,它可以将可读流的数据自动传递给可写流,自动处理背压和错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| const fs = require('fs'); const zlib = require('zlib');
const readStream = fs.createReadStream('./source.txt'); const writeStream = fs.createWriteStream('./dest.txt'); readStream.pipe(writeStream);
const gzip = zlib.createGzip(); const source = fs.createReadStream('./large-file.txt'); const compressed = fs.createWriteStream('./large-file.txt.gz');
source .pipe(gzip) .pipe(compressed);
const readable = fs.createReadStream('./input.txt'); const writable = fs.createWriteStream('./output.txt');
readable.on('error', (err) => console.error('读取错误:', err)); writable.on('error', (err) => console.error('写入错误:', err));
readable.pipe(writable);
const input = fs.createReadStream('./data.csv'); const transform = new Transform({ transform(chunk, encoding, callback) { const result = chunk.toString().toUpperCase(); callback(null, result); } }); const output = fs.createWriteStream('./data-upper.csv');
input .pipe(transform) .pipe(output);
|
6.5 Stream 实战:实现自定义转换流
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| const { Transform } = require('stream'); const fs = require('fs');
const upperTransform = new Transform({ transform(chunk, encoding, callback) { const upper = chunk.toString().toUpperCase(); callback(null, upper); } });
const lineNumberTransform = new Transform({ transform(chunk, encoding, callback) { const lines = chunk.toString().split('\n'); const numbered = lines.map((line, index) => `${index + 1}: ${line}`).join('\n'); callback(null, numbered); } });
fs.createReadStream('./input.txt') .pipe(upperTransform) .pipe(lineNumberTransform) .pipe(fs.createWriteStream('./output-numbered.txt')) .on('finish', () => { console.log('处理完成'); });
|
七、实战练习:创建文件批量重命名工具
现在我们将综合运用所学知识,创建一个实用的文件批量重命名工具。
7.1 需求分析
- 读取指定目录下的所有文件
- 根据规则批量重命名文件
- 支持预览和实际执行两种模式
- 支持递归处理子目录
- 提供清晰的进度反馈
7.2 实现代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
| #!/usr/bin/env node
const fs = require('fs'); const path = require('path');
class BatchRenamer { constructor(options = {}) { this.directory = options.directory || process.cwd(); this.pattern = options.pattern || /^(.+?)(\.\w+)?$/; this.replacePattern = options.replacePattern || null; this.recursive = options.recursive || false; this.preview = options.preview !== false; this.prefix = options.prefix || ''; this.suffix = options.suffix || ''; this.changes = []; }
scanFiles(dir) { let files = []; const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory() && this.recursive) { files = files.concat(this.scanFiles(fullPath)); } else if (entry.isFile()) { files.push(fullPath); } }
return files; }
generateNewName(filePath) { const dir = path.dirname(filePath); const ext = path.extname(filePath); const baseName = path.basename(filePath, ext);
let newBase = baseName;
if (this.replacePattern) { newBase = newBase.replace(this.replacePattern.search, this.replacePattern.replace); }
newBase = this.prefix + newBase + this.suffix;
return path.join(dir, newBase + ext); }
run() { console.log(`扫描目录: ${this.directory}`); console.log(`模式: ${this.preview ? '预览模式(不实际执行)' : '执行模式'}`); console.log(`递归: ${this.recursive ? '是' : '否'}`); console.log('---');
const files = this.scanFiles(this.directory); console.log(`找到 ${files.length} 个文件`); console.log('---');
let successCount = 0; let failCount = 0;
for (const filePath of files) { const newPath = this.generateNewName(filePath);
if (newPath === filePath) { continue; }
this.changes.push({ from: filePath, to: newPath });
if (this.preview) { console.log(`[预览] ${path.basename(filePath)} -> ${path.basename(newPath)}`); successCount++; } else { try { fs.renameSync(filePath, newPath); console.log(`[成功] ${path.basename(filePath)} -> ${path.basename(newPath)}`); successCount++; } catch (err) { console.log(`[失败] ${path.basename(filePath)}: ${err.message}`); failCount++; } } }
console.log('---'); console.log(`完成! 成功: ${successCount}, 失败: ${failCount}`); return this.changes; }
undo() { if (this.preview) { console.log('预览模式无需撤销'); return; }
let undoCount = 0; for (const change of this.changes.reverse()) { try { fs.renameSync(change.to, change.from); undoCount++; } catch (err) { console.log(`撤销失败: ${err.message}`); } } console.log(`已撤销 ${undoCount} 个重命名操作`); } }
if (require.main === module) { const renamer = new BatchRenamer({ directory: process.argv[2] || './test-files', pattern: /^(.+?)(\.\w+)?$/, replacePattern: { search: /\s+/g, replace: '_' }, recursive: true, preview: true, prefix: '', suffix: '' });
const readline = require('readline'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
renamer.run();
rl.question('确认执行以上重命名操作?(y/n): ', (answer) => { if (answer.toLowerCase() === 'y') { renamer.preview = false; renamer.run(); } else { console.log('已取消'); } rl.close(); }); }
module.exports = BatchRenamer;
|
7.3 使用方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const fs = require('fs'); const path = require('path');
const testDir = './test-files'; if (!fs.existsSync(testDir)) { fs.mkdirSync(testDir, { recursive: true }); }
const files = [ 'Document 1.pdf', 'Document 2.pdf', 'My Photo 2024.jpg', 'Project Report v2.docx', '备份 文件 名称.txt' ];
files.forEach((name, index) => { fs.writeFileSync(path.join(testDir, name), `测试文件 ${index + 1}`); });
console.log('测试文件已创建');
|
7.4 进阶改造建议
- 支持正则匹配和替换:允许用户通过命令行传入正则表达式
- 添加文件过滤:只处理特定扩展名的文件
- 生成日志:将所有操作记录到日志文件
- 支持日期批量重命名:
IMG_001.jpg → 20240923_001.jpg
- 冲突检测:处理文件名冲突的情况(自动添加序号)
- GUI 界面:使用 Electron 或 Web 技术构建图形界面
1 2 3 4 5 6 7 8 9 10 11 12
| const datedRenamer = new BatchRenamer({ directory: './test-files', replacePattern: { search: /^/, replace: new Date().toISOString().split('T')[0] + '_' }, recursive: false, preview: true });
datedRenamer.run();
|
本章小结
| 模块 |
主要功能 |
常用 API |
| fs |
文件系统操作 |
readFile, writeFile, mkdir, readdir, createReadStream, watch |
| path |
路径处理 |
join, resolve, basename, dirname, extname, parse |
| os |
操作系统信息 |
platform, cpus, totalmem, freemem, hostname, tmpdir |
| url |
URL 处理 |
URL, parse, format, resolve, searchParams |
| events |
事件驱动 |
EventEmitter, on, emit, once, removeListener |
| stream |
流式数据处理 |
Readable, Writable, Transform, pipe |
学习建议
- 多动手实践:每个模块都要编写示例代码进行测试
- 阅读官方文档:Node.js 官方文档
- 理解异步模型:重点理解回调、Promise 和 async/await 在文件操作中的应用
- 学习第三方库:了解
fs-extra、chokidar、glob 等常用工具库
- 关注性能:大文件处理使用流,避免同步操作阻塞主线程