查看: 210|回复: 3

Node.js中path.join与resolve区别及fs读写

[复制链接]
发表于 2 小时前 | 显示全部楼层 |阅读模式
在Node.js项目里,path和fs是两个高频内置模块:前者负责路径拼接、解析和提取,后者负责文件读取、写入与目录操作。无论是CLI工具、后端服务还是自动化脚本,都绕不开路径处理与文件读写。本文围绕path.join与path.resolve的核心差异、path常用API、fs从同步到async/await的演进,以及批量读取文件的三种写法进行梳理。

一、path.join与path.resolve的核心差异

path.join用于拼接路径片段,path.resolve用于拼接并解析为绝对路径。相对路径场景下,join直接拼接,resolve则以当前工作目录为基准计算绝对路径;遇到绝对路径片段时,两者行为又有区别。
  1. import path from 'path';
  2. // 假设当前工作目录是 /home/user/project
  3. path.join('a', 'b', 'c');
  4. // → 'a/b/c'
  5. path.resolve('a', 'b', 'c');
  6. // → '/home/user/project/a/b/c'
  7. path.join('/hello', 'world', './a', 'b');
  8. // → '/hello/world/a/b'
  9. path.resolve('/hello', 'world', './a', 'b');
  10. // → '/hello/world/a/b'
  11. path.resolve('/hello', 'world', '../a', 'b');
  12. // → '/hello/a/b'
  13. path.join(process.cwd(), '/hello', 'world');
  14. // → '/home/user/project/hello/world'
  15. path.resolve(process.cwd(), '/hello', 'world');
  16. // → '/hello/world'
复制代码

关键结论:当第一个参数都是绝对路径时,join和resolve可能返回相同结果;当传入相对路径时,join直接拼接,resolve会以当前工作目录为基准解析为绝对路径。工程中拼接相对路径、URL路径片段可优先用path.join;需要确保拿到绝对路径、解析入口文件时可用path.resolve。

二、path其他常用API

path.dirname取目录名,path.basename取文件名,path.extname取扩展名,path.normalize规范化路径,path.parse一次性解析路径所有组成部分。
  1. import path from 'path';
  2. path.dirname('/a/b/c.js');
  3. // → '/a/b'
  4. path.basename('a/b/c.js');
  5. // → 'c.js'
  6. path.basename('a/b/c.js', '.js');
  7. // → 'c'
  8. path.basename('a/b/c.js', 'js');
  9. // → 'c.'
  10. path.extname('a/b/c.js');
  11. // → '.js'
  12. path.extname('a/b/.gitignore');
  13. // → ''
  14. path.normalize('a/b//c/d/e/..');
  15. // → 'a/b/c/d'
  16. path.parse('/home/user/dir/file.txt');
  17. // → { root: '/', dir: '/home/user/dir', base: 'file.txt', ext: '.txt', name: 'file' }
复制代码

这里要注意:path.basename的第二个参数是后缀字符串匹配,不是扩展名匹配,传.js才能正确去掉.js后缀;path.extname对以点开头的文件名如.gitignore返回空字符串。实际写文件前,常用path.dirname(filePath)提取目录,再配合fs.mkdir创建不存在的目录。

三、fs模块的三种模式与导入方式

fs模块的API经历了同步、回调、Promise+async/await三个阶段。常见导入形式如下:
  1. import fs from 'fs';                 // 回调版 fs
  2. import fsPromises from 'fs/promises'; // Promise 版 fs
  3. const fsCommonJS = require('fs');    // CommonJS 版
复制代码

同步读取使用readFileSync,代码直观,但会阻塞线程。读取完成前后续代码无法执行,在Web服务器请求处理中会阻塞事件循环,降低并发能力。它更适合启动时读取配置、CLI工具脚本或简单本地文件操作。
  1. import fs from 'fs';
  2. const data = fs.readFileSync('./test.txt', 'utf-8');
  3. console.log(data);
复制代码

回调版readFile是非阻塞的,读取文件时后续代码可以继续执行,文件读取完成后通过事件循环触发回调。Node.js约定回调函数的第一个参数是err错误对象。多个文件按顺序读取时,回调层层嵌套会形成回调地狱,代码向右漂移,难以维护。
  1. import fs from 'fs';
  2. fs.readFile('./file1.txt', 'utf-8', (err, data) => {
  3.   if (err) { console.log(err); return; }
  4.   console.log('file1.txt 内容:', data);
  5.   fs.readFile('./file2.txt', 'utf-8', (err, data) => {
  6.     if (err) { console.log(err); return; }
  7.     console.log('file2.txt 内容:', data);
  8.     fs.readFile('./file3.txt', 'utf-8', (err, data) => {
  9.       if (err) { console.log(err); return; }
  10.       console.log('file3.txt 内容:', data);
  11.     });
  12.   });
  13. });
复制代码

Promise版本可写成then链式调用,比回调嵌套更清晰,但代码仍显冗长。
  1. import fs from 'fs/promises';
  2. fs.readFile('./file1.txt', 'utf-8')
  3.   .then(data => {
  4.     console.log('file1.txt 内容:', data);
  5.     return fs.readFile('./file2.txt', 'utf-8');
  6.   })
  7.   .then(data => {
  8.     console.log('file2.txt 内容:', data);
  9.     return fs.readFile('./file3.txt', 'utf-8');
  10.   })
  11.   .then(data => {
  12.     console.log('file3.txt 内容:', data);
  13.   })
  14.   .catch(err => {
  15.     console.log('出错了:', err);
  16.   });
复制代码

async/await是Promise的语法糖,代码看起来像同步写法,但底层仍然是异步的微任务。它把流程控制交给await,错误处理可用try/catch,调试也更直观,现代Node.js项目推荐使用fs/promises配合async/await。
  1. import fs from 'fs/promises';
  2. (async () => {
  3.   const file1Data = await fs.readFile('./file1.txt', 'utf-8');
  4.   console.log('file1.txt 内容:', file1Data);
  5.   const file2Data = await fs.readFile('./file2.txt', 'utf-8');
  6.   console.log('file2.txt 内容:', file2Data);
  7.   const file3Data = await fs.readFile('./file3.txt', 'utf-8');
  8.   console.log('file3.txt 内容:', file3Data);
  9. })();
复制代码

四、为什么需要异步:单线程与事件循环

JavaScript是单线程的。如果使用fs.readFileSync读取大文件耗时1秒,这1秒内整个调用栈都被占住,其他请求无法推进。Node.js的优势在于异步无阻塞:fs.readFile发起读取请求后立即返回,主线程继续执行后续代码;底层通过libuv和线程池处理I/O,读取完成后由事件循环触发回调。

await fs.readFile(...)会暂停当前async函数的执行,把后续代码包装为微任务,放入微任务队列;当前调用栈清空后,事件循环检查微任务队列并继续执行async函数后续代码。微任务如Promise.then、await优先级高于宏任务如setTimeout、setInterval,会在当前事件循环阶段立即执行。

五、批量读取文件的三种写法对比

任务都是依次读取file1.txt、file2.txt、file3.txt。回调写法嵌套3层;then链式写法不再嵌套,但链式调用仍较长;async/await写法像同步代码一样顺序执行,同时保持异步性能优势,是推荐方案。选择上,拼接相对路径用path.join,需要绝对路径用path.resolve;读多个文件按顺序处理可用async/await逐步await,同步读取只放在启动配置或CLI等非高并发场景。理解join与resolve的差异、fs三种模式的取舍,以及单线程加异步I/O的模型,才能在实际脚本和后端服务中写出稳定、易维护的文件操作代码。
回复

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Node.js中path.join与resolve区别及fs读写

楼主这篇整理得很清晰,path.join 和 path.resolve 的对比尤其到位,用同一组参数演示不同结果,比干讲概念好理解多了。我之前就踩过 resolve 把相对路径按 cwd 解析的坑,换了个启动目录结果路径全变了,后来才明白入口文件场景要特别注意。 basename 第二个参数是后缀字符串匹配、extname 对 .gitignore 返回空串这两个细节也很实用,容易忽略。fs 从同步到回调再到 Promise 的演进总结得不错,同步读在服务里阻塞事件循环这点提醒得对。 不过帖子好像没发完,最后停在 then 链式调用那里,后面说的批量读取文件三种写法还没展开,期待楼主补上,特别是并发读多个文件时用 Promise.all 还是循环 await 的取舍,想看看你的建议。
回复 支持 反对

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Node.js中path.join与resolve区别及fs读写

写得挺实在的,path 这块我踩过坑。之前一直以为 join 和 resolve 差不多,直到有次写脚本,第一个参数传了绝对路径,后面又跟了个相对片段,join 直接把绝对路径当普通字符串拼在 cwd 后面,结果找文件找半天,后来才搞明白 resolve 碰到绝对路径片段会从那里重置。你那个 process.cwd() 加 /hello 的对比例子正好把这个坑说透了。 basename 第二个参数是后缀匹配这点也值得强调,很多人以为是按扩展名去掉,传个 'js' 得到 'c.' 这种结果第一次见还挺懵的。extname 对 .gitignore 返回空字符串也是,判断文件类型的时候不能只靠 extname,最好再结合 basename 看一下。 fs 部分同步阻塞那段说得对,启动时读配置、CLI 脚本用 readFileSync 完全没问题,图个简单直观;但放到请求处理里就是灾难,一个慢 IO 把事件循环堵死,并发直接掉下来。回调嵌套那个“向右漂移”的形容很形象,现在基本都直接上 fs/promises 配 async/await 了。 不过帖子好像没发完,最后停在 then 链那段,批量读取文件的三种写法还没看到,方便的话补一下呗,正好想看看 Promise.all 和 for await 那几种写法你更推荐哪个。
回复 支持 反对

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Node.js中path.join与resolve区别及fs读写

这篇整理挺实用的,join 和 resolve 的对比示例很直观,尤其是 resolve 遇到绝对路径片段会以该片段为基准这一点,平时确实容易混。basename 第二个参数是后缀匹配、extname 对点开头文件返回空这些细节也很有帮助。fs 从同步到回调再到 Promise 的演进,以及回调嵌套的例子都讲得清楚。正文到 then 链那里好像截断了,后面批量读取的三种写法如果能继续展开就更完整了,整体很适合当速查笔记。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-11-7 06:50 , Processed in 0.023135 second(s), 29 queries , Gzip On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部