主题
内置模块与本地仓库操作(Day6 3/3)
本页对应 Day06_Node,共 5 段视频,约 1 小时 17 分。
学习目标
- 能够使用
require引入path、fs等内置模块,并用变量接收模块对象后调用其方法。 - 能够使用
path.join、path.resolve、path.dirname、path.basename、path.extname、path.isAbsolute完成路径拼接、解析、拆解和绝对路径判断,并说明跨平台路径分隔符差异。 - 能够使用
fs.readFile/fs.readFileSync读取文件内容,正确指定编码或调用toString()转换 Buffer,并通过错误对象或try...catch处理读取异常。 - 能够使用
fs.writeFile/fs.writeFileSync写入文件,区分覆盖写入与fs.appendFile/fs.appendFileSync追加写入,并处理写入失败。 - 能够使用
fs.rename/fs.renameSync完成文件重命名,并利用完整路径变化实现文件位置移动。 - 能够使用
fs.unlink/fs.unlinkSync删除指定文件,并对文件不存在等异常情况给出正确判断和处理。
知识点
模块引入:先 require,再使用
为什么需要:Node 中模块分为内置模块、第三方模块和自定义模块。无论哪一种,使用时都要先引入;不引入就直接调用其中的方法会报错。
怎么想:先明确要用哪个模块,再查官方文档确认模块名和方法,最后用 require 引入。require 的参数是模块名字符串,返回值是一个对象,变量名可以自定义,但一般与模块同名,便于阅读。
怎么做:
js
const path = require('path')
const fs = require('fs')易错点:不要把变量名当成固定语法;require('path') 不能写成 require(path)。引入后得到的是模块对象,方法要通过对象调用,如 path.join()、fs.readFile()。
path 模块:拼接、判断与拆解路径
为什么需要:手写路径分隔符容易出问题。Windows 使用反斜杠,Linux、macOS 等类 Unix 系统使用正斜杠;代码可能在本机开发、在服务器运行,所以要用 path 处理路径。
怎么想:拼接路径不要用字符串加号硬拼,优先用 path.join();需要把相对路径转成绝对路径时用 path.resolve();判断是否绝对路径用 path.isAbsolute();拆目录、文件名、扩展名分别用 dirname、basename、extname。
怎么做:
js
const path = require('path')
console.log(path.join('images', 'data')) // 按当前系统分隔符拼接
console.log(path.resolve('./images/data')) // 转成绝对路径
console.log(path.isAbsolute(__filename)) // true
console.log(path.dirname(__filename)) // 当前脚本所在目录
console.log(path.basename(__filename)) // 当前脚本文件名
console.log(path.extname(__filename)) // .js易错点:join 主要是纯拼接,不保证结果是绝对路径;resolve 会基于当前脚本所在目录把相对路径转成绝对路径。转写稿中调试时曾把 __filename 拼进路径,结果路径指到脚本文件后再拼 data,明显不对;后来改用 __dirname 才正确。join 拼接结果的分隔符会随操作系统变化,这正是它比手写斜杠更稳妥的原因。
fs 读取文件:异步优先,注意 Buffer 与错误
为什么需要:fs 是文件系统模块,用来读写文件和目录。读取文件时,Node 提供同步和异步两种方式;异步不会阻塞后续代码,更适合服务器场景。
怎么想:优先用异步 readFile;回调函数第一个参数是错误对象 err,第二个参数是数据 data。如果不指定编码,data 是 Buffer,需要 toString() 或读取时指定 utf8。路径建议用 path.join(__dirname, ...) 拼成绝对路径,更稳。
怎么做:
js
const fs = require('fs')
const path = require('path')
const fileName = path.join(__dirname, 'data', 'a.txt')
fs.readFile(fileName, 'utf8', (err, data) => {
if (err) {
console.log('文件读取失败', err.errno, err.code)
return
}
console.log('文件读取成功', data)
})同步写法用 readFileSync,并配合 try/catch:
js
try {
const data = fs.readFileSync(fileName, 'utf8')
console.log(data)
} catch (err) {
console.log('文件读取失败', err.errno, err.code)
}易错点:异步回调里判断 err 后要 return,否则失败后仍可能继续执行成功逻辑;不指定编码会读到 Buffer;路径拼错时 err 是对象,可查看 errno 和 code。转写稿中还演示了错误路径下输出“文件读取失败”,说明错误处理要放在回调内部。
fs 写入文件:writeFile 覆盖,appendFile 追加
为什么需要:实际开发中要保存日志、生成文件、写临时数据。writeFile 用于写入,appendFile 用于追加。
怎么想:先明确是覆盖还是追加。writeFile 在文件不存在时会创建,文件已存在时会覆盖原内容;appendFile 则在原内容后追加。异步回调只接收一个 err 参数。
怎么做:
js
const fs = require('fs')
const path = require('path')
const fileName = path.join(__dirname, 'data', 'b.txt')
const content = '你好,小乐\n'
fs.writeFile(fileName, content, (err) => {
if (err) {
console.log('写入失败', err.errno, err.code)
return
}
console.log('写入成功')
})
fs.appendFile(fileName, content, (err) => {
if (err) {
console.log('追加失败')
return
}
console.log('追加成功')
})同步写入可用 writeFileSync、appendFileSync,并用 try/catch 包裹。
易错点:writeFile 不是追加,重复运行会覆盖;文件不存在时通常会创建,所以“写不进去”更可能是路径拼错。转写稿中写入失败就是路径拼接有问题,输出文件名后发现拼错,改用 __dirname 或 path.resolve 后成功。Buffer 写入也会按编码处理,例如写入数字 100,显示为字母 d,因为 100 是字符 d 的编码。
rename 与 unlink:重命名、移动和删除
为什么需要:文件管理常涉及改名、移动位置和删除。rename 可改文件名,也可改变完整路径,因此能移动文件;unlink 用于删除文件。
怎么想:从编程角度看,文件名是完整路径的一部分,rename(oldPath, newPath) 改的是完整路径,所以既能重命名也能移动。删除用 unlink(path, callback)。二者都有同步版本。
怎么做:
js
const fs = require('fs')
const path = require('path')
const oldPath = path.join(__dirname, 'data', 'a.txt')
const newPath = path.join(__dirname, 'data', 'a.md')
fs.rename(oldPath, newPath, (err) => {
if (err) {
console.log('重命名失败', err.errno, err.code)
return
}
console.log('重命名成功')
})
fs.unlink(newPath, (err) => {
if (err) {
console.log('删除失败', err.errno, err.code)
return
}
console.log('删除成功')
})易错点:重复运行重命名会失败,因为旧文件已不存在;重复删除也会失败。异步回调中失败后要 return。同步版本如 renameSync、unlinkSync 可配合 try/catch,但实际开发仍优先考虑异步,避免阻塞。
从“查文档 + 引入模块”开始
本节从 Node.js 内置模块 path 和 fs 入手,完成本地文件的路径处理、读取、写入、重命名与删除。为什么需要先讲这些?因为文件操作离不开路径,而不同操作系统对路径分隔符的处理并不一样;如果手写字符串拼接,很容易在 Windows 与 Linux 之间出现兼容问题。怎么想?先把“路径处理”和“文件读写”分开:path 负责算路径,fs 负责真正读写文件。怎么做?使用内置模块前必须先引入,并用变量保存模块对象:
js
const path = require('path');
const fs = require('fs');require 的参数是模块名,返回值是模块对象。易错点:模块不引入就不能用;变量名可以自定义,但通常写成 path、fs 便于阅读。转写稿里反复提到“pass”,实际是 path;“照/周义/周尾”是 path.join,“resso”是 path.resolve。
path.join:拼接路径,自动适配系统分隔符
为什么需要 path.join?因为路径拼接不是简单加字符串。怎么想?把多个路径片段交给它,它会按当前系统规则拼接,并处理多余分隔符。怎么做:
js
const p1 = path.join('image', 'data');
const p2 = path.join(__dirname, 'data', 'a.txt');
console.log(p1, p2);转写稿中演示了 path.join(__dirname, 'data', 'a.txt'),输出会是当前脚本所在目录下的完整路径。易错点:__filename 是当前脚本文件的完整路径,不是目录;拼同级目录要用 __dirname。转写稿里写入文件失败,正是因为把 __filename 当成目录继续拼 data/b.txt,结果拼到了 02-写入文件.js/data/b.txt 这种错误位置。调试时先 console.log(fileName) 看拼出来的路径,再改成 __dirname 或 path.resolve。
path 的其他常用方法
为什么还要 isAbsolute、dirname、basename、extname?因为实际开发经常要判断和拆解路径。怎么想?把完整路径拆成“目录、文件名、扩展名”分别处理。怎么做:
js
console.log(path.isAbsolute('a.jpg')); // false
console.log(path.isAbsolute(__filename)); // true
console.log(path.dirname(__filename)); // 目录部分
console.log(path.basename(__filename)); // 文件名部分
console.log(path.extname(__filename)); // .js易错点:isAbsolute 返回布尔值;basename 只取最后一段文件名,不包含目录。path.resolve 则会把相对路径转成绝对路径,并基于当前脚本所在目录解析:
js
console.log(path.resolve('./image/data'));它和 join 的区别是:join 主要做拼接,resolve 会得到绝对路径。转写稿建议:不确定路径时,用 path.resolve 把相对路径转绝对路径,准确性和稳定性更好。
fs.readFile:异步读取文件
为什么读取文件要用 fs?因为 fs 是文件系统模块,能读写文件和目录。怎么想?读取分同步和异步,推荐异步,避免阻塞后续代码。怎么做:
js
const fs = require('fs');
const path = require('path');
const fileName = path.join(__dirname, 'data', 'a.txt');
fs.readFile(fileName, 'utf8', (err, data) => {
if (err) {
console.log('文件读取失败', err.code, err.errno);
return;
}
console.log(data);
});转写稿先演示了不传编码时得到 Buffer,再用 data.toString() 转字符串;更推荐第二个参数直接写 'utf8'。易错点:回调第一个参数是错误对象,第二个才是数据;err 为空表示成功。若出错后不 return,后面的成功逻辑还可能继续执行。
fs.readFileSync 与 try/catch
同步读取为什么还要了解?有些场景需要按顺序立即拿到结果。怎么做:
js
try {
const data = fs.readFileSync(fileName, 'utf8');
console.log(data);
} catch (err) {
console.log('文件读取失败', err.code, err.errno);
}易错点:同步会阻塞后续代码;同步读取失败通常要用 try/catch 捕获。转写稿强调:推荐异步,但同步方式要会看、会写。
fs.writeFile:写入会覆盖原内容
为什么用 writeFile?因为它能把字符串或 Buffer 写入文件。怎么做:
js
const fileName = path.join(__dirname, 'data', 'b.txt');
const content = '你好,小乐儿\n';
fs.writeFile(fileName, content, (err) => {
if (err) {
console.log('写入失败', err.code, err.errno);
return;
}
console.log('写入成功');
});易错点:文件不存在时会自动创建;文件已存在时会覆盖原内容,不是追加。转写稿中还演示了写入 Buffer,数字 100 最终显示为字母 D,因为写入时会按编码处理。
fs.appendFile:追加写
为什么需要追加?因为 writeFile 会覆盖。怎么做:
js
fs.appendFile(fileName, '追加一行\n', (err) => {
if (err) {
console.log('写入失败');
return;
}
console.log('写入成功');
});同步追加可用 fs.appendFileSync。转写稿用循环追加一千行、一万行,说明追加不会清空原文件。易错点:追加写仍要判断 err。
rename 与 unlink:重命名、移动和删除
fs.rename(oldPath, newPath, callback) 既能重命名,也能移动文件位置,因为完整文件名包含路径。转写稿把 data/a.txt 改成 a.md,又把文件移动到外层目录,均成功。易错点:旧路径不存在会失败。
删除用 fs.unlink(filePath, callback):
js
fs.unlink(path.join(__dirname, 'a.md'), (err) => {
if (err) {
console.log('文件删除失败');
return;
}
console.log('文件删除成功');
});易错点:第一次删除成功后,第二次再运行会失败,因为文件已不存在。rename、unlink 也都有同步版本,如 renameSync、unlinkSync。整体原则:能用异步就用异步;路径优先用 path.join 或 path.resolve 生成绝对路径,出错时先打印路径和错误对象再排查。
材料说明与本节范围
给定笔记标题是“2 远程仓库有关的操作”,但转写稿实际讲解的是 Node.js 内置模块 path 与 fs 的文件操作,并没有出现远程仓库相关内容。为避免编造,下面按转写稿整理本节教程正文;远程仓库部分因材料缺失不补写。转写稿中也没有可保留的图片,故只整理其中的代码与调试过程。
path 模块:先引入再使用
为什么需要:路径拼接不能直接靠字符串加号,因为 Windows 用反斜杠、Linux/macOS 用正斜杠,写死分隔符容易在部署后出错。
怎么想:内置模块要先 require 引入,拿到模块对象后再调用方法。
怎么做:
js
const path = require('path')
const filename = path.join(__dirname, './data/a.txt')
console.log(filename)易错点:require('path') 不能漏;变量名可自定义,但一般用 path。path.join 是纯拼接,不会自动变成绝对路径;path.resolve 才会把相对路径解析成绝对路径。path.isAbsolute 判断是否绝对路径;path.dirname、path.basename、path.extname 分别取目录、文件名、扩展名。
fs 读取文件:异步优先,注意 Buffer 与错误对象
为什么需要:读文件需要时间,同步读取会阻塞后续代码;异步读取把回调放入回调队列,等同步任务执行完再执行。
怎么想:先拼绝对路径,再用 fs.readFile;回调第一个参数是错误对象,第二个是数据。
怎么做:
js
const fs = require('fs')
const path = require('path')
const filename = path.join(__dirname, './data/a.txt')
fs.readFile(filename, 'utf8', (err, data) => {
if (err) {
console.log('文件读取失败', err.errno, err.code)
return
}
console.log(data)
})转写稿中的调试过程是:不指定编码时读到的是 Buffer,直接打印会出现二进制内容,需要 data.toString() 或传 'utf8'。文件地址写错时,err 是对象,可取 errno 和 code;加 return 可避免错误后继续执行。同步读取用 fs.readFileSync,出错时用 try/catch 捕获。
易错点:异步回调里不要漏掉 return;不要把 err 当字符串;同步读取会阻塞,推荐异步。
fs 写入文件:覆盖、追加与路径错误
为什么需要:程序经常要生成或更新文件。
怎么想:fs.writeFile 默认覆盖原内容,文件不存在会自动创建;追加用 fs.appendFile。
怎么做:
js
const fs = require('fs')
const path = require('path')
const filename = path.join(__dirname, './data/b.txt')
const data = '你好,小乐儿\n'
fs.writeFile(filename, data, (err) => {
if (err) {
console.log('写入失败', err.errno, err.code)
return
}
console.log('写入成功')
})转写稿中写入失败的调试关键:用 __filename 拼路径会拼到当前 JS 文件后面,导致路径错误;应改用 __dirname,或用 path.resolve('./data/b.txt')。同步写入用 fs.writeFileSync,可配合 try/catch。追加写可用 fs.appendFile 或 fs.appendFileSync,循环追加可写多行。写入 Buffer 时会被编码,例如数字 100 可能显示为字符 d。
易错点:writeFile 是覆盖不是追加;路径拼错是常见失败原因;同步写入失败要用 try/catch。
文件重命名与删除:rename 也能移动位置
为什么需要:文件管理需要改名、移动、删除。
怎么想:fs.rename(oldPath, newPath, callback) 可改文件名,也可改变完整路径,因此能移动文件。fs.unlink(path, callback) 删除文件。
怎么做:
js
fs.rename('./data/a.txt', './data/a.md', (err) => {
if (err) return console.log('重命名失败')
console.log('重命名成功')
})
fs.unlink('./data/a.md', (err) => {
if (err) return console.log('删除失败')
console.log('删除成功')
})易错点:再次运行 rename 会因旧文件不存在而失败;删除不存在的文件也会失败。同步版本分别是 renameSync、unlinkSync。
课堂画面





代码示例
示例 1:path 内置模块——拼接与拆解路径
js
// path-demo.js
// 第一步:引入内置模块,用变量接住 require 返回的对象
const path = require('path');
// 1) join:拼接路径
console.log(path.join('images', 'date')); // images\date(Windows)
console.log(path.join(__dirname, './data', 'images')); // 绝对路径 + 后面的片段
console.log(path.join(__dirname, 'data', '..', 'images')); // .. 会往回退一步
// 2) isAbsolute:判断是不是绝对路径,返回 true / false
console.log(path.isAbsolute('./images/100.jpg')); // false
console.log(path.isAbsolute(__filename)); // true
// 3) 拆解路径中的各个部分
console.log(path.dirname(__filename)); // 目录部分(不带文件名)
console.log(path.basename(__filename)); // 文件名部分 path-demo.js
console.log(path.extname(__filename)); // 扩展名部分 .js
// 4) resolve:把相对路径解析成绝对路径(以当前脚本所在目录为基准)
console.log(path.resolve('./images/date')); // C:\...\images\date
console.log(path.join('./images', 'date')); // images\date —— 只是拼接关键行与易错点
require('path')的参数是模块名字符串,返回值是一个对象;不引入就不能用,变量名叫什么都行,习惯上叫path。__dirname是当前脚本所在目录的绝对路径,__filename是当前脚本的绝对路径,用它们拼路径才“稳”。join只是单纯拼接,它会按当前操作系统自动选分隔符:Windows 是反斜杠`,Linux/macOS 是正斜杠/。所以不要手写`,否则代码挪到 Linux 服务器上就不认识了。resolve比join更进一步:它会把相对路径转成绝对路径,./、../都会基于脚本所在目录解析;join不会转绝对路径。dirname / basename / extname分别是目录、文件名、扩展名,注意basename不含目录,extname含那个点。
示例 2:fs 读取文件——异步(推荐)+ 同步(try/catch)
js
// fs-read.js
const fs = require('fs');
const path = require('path');
// 先把要读的文件准备好(真实项目里这个文件本来就存在)
fs.mkdirSync(path.join(__dirname, 'data'), { recursive: true });
fs.writeFileSync(path.join(__dirname, 'data', 'a.txt'), '锄禾日当午\n粒粒皆辛苦', 'utf-8');
// 用绝对路径读取:效率更高、准确性更高,程序挪位置也不怕
const fileName = path.join(__dirname, 'data', 'a.txt');
// ---------- 异步读取(推荐) ----------
fs.readFile(fileName, 'utf-8', (err, data) => {
if (err) {
console.log('文件读取失败');
console.log(err.errno, err.code); // 错误编号 / 错误代码
return; // 关键:不 return,后面的代码还会继续跑
}
console.log('读取到的内容:', data);
});
console.log('hello 高晓乐'); // 这行会先执行,因为读文件是异步的
// ---------- 同步读取(了解,会阻塞) ----------
try {
const data = fs.readFileSync(fileName, 'utf-8');
console.log('同步读取:', data);
} catch (err) {
console.log('文件读取失败');
console.log(err.errno, err.code);
}关键行与易错点
readFile(路径, 编码, 回调)三个参数,中间编码可省略;省略时data是Buffer(一堆十六进制),要再调data.toString()才是字符串,所以直接写'utf-8'更省事。- 回调第一个参数
err是错误对象:没出错时是null,出错时才有值,可以取err.errno(错误编号)和err.code(错误代码)。 err判断后一定记得return,否则“读取失败”和“读取成功”会一起打印。- 同步方法都带
Sync后缀,有返回值、没有回调,用try...catch兜异常;它会阻塞,转写稿里反复强调:能用异步就用异步。 - 打印顺序是:
hello 高晓乐→同步读取→读取到的内容,理解这个顺序就理解了“非阻塞”。
示例 3:fs 写入 / 追加 / 重命名 / 删除
js
// fs-write.js
const fs = require('fs');
const path = require('path');
fs.mkdirSync(path.join(__dirname, 'data'), { recursive: true });
// 准备一个待重命名、待删除的文件
fs.writeFileSync(path.join(__dirname, 'data', 'a.txt'), '待重命名', 'utf-8');
// 写入地址用 resolve 转成绝对路径,别再用 __filename 拼
const fileName = path.resolve('./data/b.txt');
const data01 = '你好,小乐儿\n' + Math.random() + '\n';
// ---------- 异步写入:文件不存在会自动创建,存在则覆盖 ----------
fs.writeFile(fileName, data01, (err) => {
if (err) {
console.log('写入失败', err.errno, err.code);
return;
}
console.log('写入成功');
// 写入完成后再追加,避免两个异步操作抢同一个文件
fs.appendFile(fileName, '追加的一行\n', (err2) => {
if (err2) {
console.log('追加失败', err2.errno, err2.code);
} else {
console.log('追加成功');
}
});
});
// ---------- 重命名(也能用来移动文件位置) ----------
fs.rename(
path.join(__dirname, 'data', 'a.txt'),
path.join(__dirname, 'data', 'a.md'),
(err) => {
console.log(err ? '重命名失败' : '重命名成功');
}
);
// ---------- 删除文件 ----------
fs.unlink(path.join(__dirname, 'data', 'a.md'), (err) => {
if (err) {
console.log('文件删除失败', err.errno, err.code);
} else {
console.log('文件删除成功');
}
});关键行与易错点
writeFile的回调只接收一个err参数(写入没有“读出来的数据”),这和readFile的两个参数不一样。writeFile是覆盖写:文件不存在就创建,已存在就把原内容清空重写;想追加要用appendFile。- 拼路径最容易翻车:转写稿里就是因为用了
__filename(指向.js文件本身)去拼,结果写到了错误位置,应改用__dirname或path.resolve()。 - 从编程角度看,“重命名”和“移动位置”是一回事:
rename改的是完整路径,只改最后一段就是改名,换掉前面目录就是移动。 unlink只能删文件,删完再运行一次必然失败(文件已不存在),所以错误分支一定要处理。- 同步版本对应
writeFileSync / appendFileSync / renameSync / unlinkSync,需要容错时同样用try...catch包起来。
常见错误
- 现象:直接调用
path.join或fs.readFile报未定义;原因:使用内置模块前未require引入;修法:先const path = require('path')/const fs = require('fs'),再用变量调用。 - 现象:拼接出的文件路径多出或缺少目录,读写失败;原因:用
__filename当目录拼接,或相对路径基准理解错误;修法:目录拼接用__dirname,需要绝对路径时用path.resolve。 - 现象:
fs.readFile读出的内容是 Buffer,直接当字符串处理出现乱码;原因:异步读取未指定编码;修法:在参数中指定'utf8',或对 Buffer 调用toString()。 - 现象:读取失败后仍继续执行后续成功逻辑;原因:回调中判断
err后没有return;修法:错误分支输出信息后立即return,或把成功逻辑放到else中。 - 现象:异步读写代码的输出顺序与书写顺序不一致;原因:把异步回调当成同步代码理解;修法:依赖回调/后续流程处理结果,不要假设回调前的代码会等它完成。
- 现象:写入文件后原内容消失,或追加内容不符合预期;原因:误把
writeFile当追加写,或误把appendFile当覆盖写;修法:覆盖用writeFile,追加用appendFile,并明确文件不存在时的创建行为。 - 现象:重命名/移动文件失败或删错文件;原因:
rename的旧路径、新路径不是完整目标路径,删除前未确认文件存在;修法:用绝对路径明确源和目标,删除前判断存在性并处理错误。
练习
- 使用
path模块拼接当前脚本目录下data/a.txt的绝对路径,并输出结果。 - 使用
fs异步读取一个文本文件,指定utf8编码,并分别处理读取成功和失败。 - 分别用覆盖写和追加写向同一个文件写入内容,运行两次后观察并解释文件内容变化。
- 使用
fs.rename将data/a.txt移动到当前脚本目录下,并处理源文件不存在的情况。 - 使用同步方式删除一个文件,并用
try/catch处理删除失败。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 内置模块path | 15:32 |
| 2 | 读取文件 | 39:46 |
| 3 | 写入文件 | 14:24 |
| 4 | 文件重命名 | 05:14 |
| 5 | 文件删除 | 02:10 |
小结
- 内置模块使用前必须先引入,变量名可自定义,但后续调用要一致。
- 面对大量 API 不必全记,应先查文档,再挑常用方法解决实际问题。
- 路径拼接优先使用
path模块,涉及文件读写时优先构造绝对路径。 - 文件读写要明确同步与异步的差异,实际开发中优先考虑异步方式。
- 异步操作必须认真处理错误对象,错误分支要及时终止后续成功逻辑。
- 写入文件要区分覆盖写与追加写,避免误用导致数据丢失或重复。
- 同步代码可用
try/catch提升健壮性,但不能把它当成异步回调的替代。 rename不仅能改文件名,也能改变文件所在路径,本质是改变完整文件名。