Menu

Node.js path 模块:path.join 与 path.resolve 的区别

path 模块按照操作系统的规则拼接和拆分文件路径。path.join 把路径片段拼在一起;path.resolve 则从当前文件夹开始,把它们变成一个绝对路径。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

path.join() 用正确的分隔符拼接路径片段,并清理 .. 和重复的斜杠;如果第一个片段是相对路径,结果也保持相对路径。path.resolve() 从右往左做同样的工作,并且总是返回绝对路径:它从当前工作目录开始,途中遇到任何绝对路径片段都会从那里重新开始。拼接路径用 join,需要绝对路径时用 resolve。

它打印了 users/ada/notes.txt,然后是 users/grace/todo.txt(.. 去掉了 ada),接着是 /srv/app/public/index.html 和 src/lib/utils.js:多余的斜杠和空片段都被清理掉了。join 从不把 /public/ 当作重新开始,它只是下一个片段。

path.join 与 path.resolve 的区别

当某个片段以 / 开头,或者第一个片段是相对路径时,区别就显现出来了:

/public 位于中间时,join 得到 /srv/app/public/img,而 resolve 丢掉了它之前的所有内容,得到 /public/img;两者处理 .. 的方式相同。最后两行表明,join 让 public/index.html 保持为相对路径,而 resolve 在前面加上当前文件夹,把它变成了绝对路径。

path.joinpath.resolve
结果相对或绝对,与输入一致总是绝对路径
以 / 开头的片段只是普通的一个片段从这里重新开始
起点无当前工作目录
不传参数'.'当前工作目录
典型用途用几部分拼出一个路径把用户输入或配置变成绝对路径

basename、dirname、extname 和 parse

这些函数用来拆分路径。它们只看字符串本身:文件不必真的存在。

它打印了 cat.final.jpg、cat.final、/home/ada/photos 和 .jpg:extname 只取最后一个点之后的部分。path.parse 一次返回所有部分(root、dir、base、ext、name),path.format 则把这样的对象变回字符串。

__dirname 和 __filename

在 CommonJS 文件中,__dirname 是包含该文件的文件夹的绝对路径,__filename 则是文件本身的绝对路径。'data.json' 这样的相对路径是相对于用户运行 node 的位置,只要有人从别的文件夹启动你的脚本,它就会出错。所以要基于 __dirname 来拼接路径:

它打印了 config.json dark 和 index.js。完整路径在每台机器上都不一样,这正是要用 path.join(__dirname, ...) 拼接路径而不是手写的原因。

ES 模块中的 __dirname:import.meta.dirname

ES 模块中没有 __dirname;使用它会抛出 ReferenceError: __dirname is not defined in ES module scope。从 Node.js 20.11 开始,可以用 import.meta.dirname 和 import.meta.filename 代替:

它打印了 index.js 和 data/users.json。在 20.11 之前的 Node.js 版本中,要从模块 URL 推导出文件夹:path.dirname(fileURLToPath(import.meta.url)),其中 fileURLToPath 来自 node:url。

为什么不用字符串拼接路径

'data' + '/' + file 在 macOS 和 Linux 上能用,到了分隔符是 \ 的 Windows 上就会出错,而且它对 ..、重复的斜杠或缺少的斜杠都无能为力。path 遵循它所运行的系统的规则。用 path.win32,在任何机器上都能看到 Windows 上的结果:

const path = require('path');

path.posix.join('users', 'ada', 'notes.txt'); // 'users/ada/notes.txt'
path.win32.join('users', 'ada', 'notes.txt'); // 'users\\ada\\notes.txt'
path.sep;                                     // '/' on macOS and Linux, '\\' on Windows

把 path.join 的结果传给 fs.readFile,同一份代码在每个系统上都能读取文件。

常见错误

以为 extname 认识双重扩展名。 path.extname('archive.tar.gz') 是 '.gz',而 .env 这样的点文件根本没有扩展名:path.extname('.env') 是 ''。当完整的后缀很重要时,自己检查文件名的结尾。

以为 join 能把用户限制在某个文件夹内。 path.join('/srv/app', '../../etc/passwd') 的结果是 '/etc/passwd'。在读取由请求指定的文件之前,先 resolve 路径,再检查它是否仍以你的文件夹开头。

常见问题

__dirname 和 process.cwd() 有什么区别?

__dirname 是当前源文件所在的文件夹,永远不会变。process.cwd() 是用户运行 node 时所在的文件夹。只有从程序自己的文件夹启动它时,两者才相同。

和 __dirname 一起用时,该用 path.join 还是 path.resolve?

对于 path.join(__dirname, 'data.json'),两者都可以,因为 __dirname 本身已经是绝对路径。如果某个片段本身可能就是绝对路径(比如用户输入的路径),而你希望以它为准,就用 resolve。

如何用 path 模块处理 URL?

不要这样做:URL 永远使用 /,对 .. 和查询字符串也有自己的规则。请使用 URL 类,例如 new URL('../img/logo.png', 'https://example.com/docs/page')。

Coddy 编程语言插图

和 Coddy 一起学编程

开始