Node.js 简介

path 路径模块

掌握 Node.js path 模块拼接和解析路径的常用方法,分清 __dirname 与 process.cwd() 的区别。

🎯 引言

学完这篇文章,你能用 path 模块正确拼接和解析文件路径,说清楚 __dirnameprocess.cwd() 的区别,从此不再被“文件明明存在却读取失败”的问题困扰。


🧱 为什么需要 path 模块

不同操作系统的路径写法不一样:Windows 用反斜杠 \,macOS 和 Linux 用正斜杠 /。如果用字符串硬拼路径:

// 在 Windows 上可能出问题,换个系统就失效
const file = './logs' + '/' + 'app.log';

path 是 Node.js 内置的路径处理模块,它会根据当前系统自动选择正确的分隔符,还能帮我们解析路径的各个部分。引入方式:

import path from 'node:path';

🛠 拼接路径:join 与 resolve

path.join 把多段路径拼成一个,自动处理分隔符和多余的斜杠:

join.js
import path from 'node:path';

const logFile = path.join('./logs', '2024', 'app.log');
console.log(logFile); // logs/2024/app.log(Windows 上为 logs\2024\app.log)

path.resolve 则会基于当前工作目录计算出完整的绝对路径:

resolve.js
import path from 'node:path';

const fullPath = path.resolve('./logs', 'app.log');
console.log(fullPath); // /Users/你/项目目录/logs/app.log
区别记法:join 只做拼接,结果是相对的还是绝对的取决于你传入的内容;resolve 一定会返回绝对路径。项目里定位文件通常用 resolve 更稳妥。

🛠 解析路径:basename、dirname、extname、parse

拿到一个路径字符串,想拆出其中的某部分,用这组方法:

parse.js
import path from 'node:path';

const file = '/user/local/demo/index.html';

console.log(path.basename(file)); // 文件名:index.html
console.log(path.dirname(file)); // 所在目录:/user/local/demo
console.log(path.extname(file)); // 扩展名:.html

console.log(path.parse(file));
// { root: '/', dir: '/user/local/demo', base: 'index.html', ext: '.html', name: 'index' }

parse 一次拆出所有部分,适合需要同时用到好几样的场景。


⚡ __dirname 与 process.cwd() 的区别

这是初学者很容易混淆的两个值:

  • __dirname当前这个 js 文件所在的目录,是固定不变的。
  • process.cwd()你在终端哪个目录下执行的 node 命令,跟着你的执行位置变。

做个小实验,在项目根目录下创建 demo/show.js

demo/show.js
console.log('__dirname:', __dirname);
console.log('cwd:', process.cwd());

项目根目录执行 node demo/show.js,输出类似:

__dirname: /Users/你/project/demo
cwd:      /Users/你/project

文件在 demo 目录里,但你站在根目录执行,两个值就不一样了。

用相对路径读写文件时,相对的是 process.cwd(),而不是当前文件的位置。在根目录执行 node demo/show.js,代码里的 ./data.txt 会去根目录找,而不是 demo 目录。想稳定定位文件,用 path.join(__dirname, 'data.txt')

💡 ESM 中没有 __dirname

前面模块化一篇提过,ES Module 里没有 __dirname。Node.js 20.11 之后可以直接用 import.meta 上的两个属性:

show.mjs
console.log('当前文件所在目录:', import.meta.dirname);
console.log('当前文件完整路径:', import.meta.filename);

有了 import.meta.dirname,拼路径的方式和 CommonJS 完全一样:

import path from 'node:path';

const dataFile = path.join(import.meta.dirname, 'data.txt');

🧾 小节总结

  • path 模块按当前系统自动处理路径分隔符,避免手写字符串拼接的兼容问题。
  • join 只负责拼接,resolve 会基于工作目录返回绝对路径。
  • basenamedirnameextnameparse 用于拆解路径的各个部分。
  • __dirname 是文件所在目录,process.cwd() 是执行 node 命令时所在的目录。
  • 相对路径读写文件相对于 cwd,定位文件建议用 path.join(__dirname, ...);ESM 中用 import.meta.dirname

❓ 知识问答

Q1:join 和 resolve 到底用哪个?

想拼出绝对路径定位项目文件,用 resolvejoin(__dirname, ...);只是想简单拼几段路径字符串,用 join。两者都不会校验路径是否真实存在。

Q2:为什么我在根目录执行脚本,相对路径就找不到文件了?

因为相对路径相对于 process.cwd()(你执行命令的位置),而不是文件自身位置。改用 path.join(__dirname, '文件名') 就能解决。

Q3:ESM 里想用 __dirname 怎么办?

Node.js 20.11 之后直接用 import.meta.dirname。更老的版本需要通过 import.meta.url 配合 fileURLToPath 转换,升级 Node 版本后就不需要了。

Q4:path 模块能判断路径存不存在吗?

不能,path 只负责处理字符串。判断文件是否存在要用 fs 模块,比如 fs.existsSynctry/catch


🧪 小练习

假设你的脚本位于 project/tools/clean.js,要求:无论用户在哪个目录下执行 node tools/clean.js,都能正确拼出 project/logs/app.log 的绝对路径并打印。

tools/clean.js
import path from 'node:path';

// 请在这里编写代码

🎉 恭喜你已经掌握路径处理的核心知识!下一篇我们用 http 模块搭建一个真正的 Web 服务器。