大家好,我是若川。欢迎加我微信 ruochuan12,加群交流学习。今天分享一篇nodejs基础的文章。点击下方卡片关注我,或者查看源码等系列文章。
在日常使用 Node 进行开发的时候,会使用到一些文件系统、路径操作等基础 API,这里整理一下,方便大家理解和直接使用。
这里只介绍最常用的那些,不是所有哈,想要看更全的,直接看官方文档[1]就 OK。
尽量不废话,多上代码。
Process 模块
先介绍 process 模块,它提供了当前 Node 进程相关的全局环境信息。在后面的 API 中被用到。
// 内置模块,直接使用
const process = require('process');
process.cwd()
这是一个函数,返回当前 Node 进程执行的目录,举例一个常见的场景:
一个 Node 模块 A
通过 NPM 发布,项目 B
中使用了模块 A
。在 A
中需要操作 B
项目下的文件时,就可以用 process.cwd()
来获取 B
项目的路径。
const cwd = process.cwd(); // 输出:/Users/xiaolian/Code/node-api-test
process.argv
在终端通过 Node 执行命令的时候,通过 process.argv
可以获取传入的命令行参数,返回值是一个数组:
0: Node 路径(一般用不到,直接忽略)
1: 被执行的 JS 文件路径(一般用不到,直接忽略)
2~n: 真实传入命令的参数**
所以,我们只要从 process.argv[2]
开始获取就好了。一般都是这样用:
const args = process.argv.slice(2);
直接获取我们想要的参数。
process.env
返回一个对象,存储当前环境相关的所有信息,一般很少直接用到。
一般我们会在 process.env
上挂载一些变量标识当前的环境。比如最常见的用 process.env.NODE_ENV
区分 development
和 production
。在 vue-cli
的源码中也经常会看到 process.env.VUE_CLI_DEBUG
标识当前是不是一 DEBUG
模式。
这里提一个 webpack 的插件 DefinePlugin[2],在日常的构建流程中,我们经常会通过这个插件来注入不同的全局变量,从而执行不同的构建流程,并且代码中的 process.env.xxx
会被替换成具体的值,在 Terser 压缩阶段会将 deadCode 移除,优化代码体积。
process.platform
这个用的不多,返回当前系统信息,枚举值如下:
console.log(process.platform);// 'aix'
// 'darwin' - macOS
// 'freebsd'
// 'linux' - linux
// 'openbsd'
// 'sunos'
// 'win32' - windows
Path 模块
// 内置模块,直接使用
const path = require('path');
Node 中几乎路径相关的操作都会使用这个模块。
这里就说 5 个最常用的:
path.join(...paths)
path.join
作用是将传入的多个路径拼成一个完整的路径。
const dPath = path.join('template', 'aaa', 'bbb', 'ccc', 'd.js');
// 输出: template/aaa/bbb/ccc/d.js
来看一个非常常见的场景,我们需要获取当前项目的 package.json 文件,就可以这样获取它的路径:
const pkgPath = path.join(process.cwd(), './package.json');
// 输出: /Users/xiaolian/Code/node-api-test/package.json
path.join
可以传入任意个路径,比如:
['package.json', 'README.md'].forEach(fileName => {const templateFilePath = path.join(process.cwd(), 'template', fileName);console.log(templateFilePath);
});
// 输出: /Users/xiaolian/Code/node-api-test/template/package.json
// 输出: /Users/xiaolian/Code/node-api-test/template/README.md
path.resolve(...paths)
path.resovle
和 path.join
的区别在于它的作用是将传入的多个路径和当前执行路径拼接成一个完整的绝对路径。
假设我现在 index.js
在 scripts
目录下,然后我在根目录下执行 node scripts/index.js
,它的代码如下:
const dPath = path.resolve('aaa', 'bbb', 'ccc', 'd.js');
// 输出: /Users/xiaolian/Code/node-api-test/aaa/bbb/ccc/d.js
一般情况下,当 path.resolve
的第一个参数为 ./
时,可以直接理解和 path.join(processs.cwd(), '')
表现一致。
path.basename(path[, ext])
path.basename
返回指定 path
最后一个路径名,其中第二个参数 ext
可选,表示文件扩展名。比如:
console.log(path.basename('scripts/index.js')); // index.js
console.log(path.basename('scripts/index.js', '.js')); // 匹配到 .js,返回 index
console.log(path.basename('scripts/index.js', '.json')); // 没匹配到,返回 index.js
path.dirname(path)
和 path.basename
对应,返回指定 path
最后一个路径名之前的路径。比如:
console.log(path.basename('scripts/index.js')); // scripts
console.log(path.basename('scripts/hook/index.js')); // scripts/hook
path.extname(path)
和 path.basename
对应,返回指定 path
最后一个路径名的文件扩展名(含小数点 .
)。比如:
console.log(path.basename('scripts/index.js')); // .js
console.log(path.basename('README.md')); // .md
对比
最后再来对比一下各个路径相关的 API 的区别。
项目 A
的目录结构如下:
├── scripts
│ └── index.js
├── src
│ └── index.js
├── package.json
├── README.md
scripts/index.js
的代码如下:
const path = require('path');console.log(path.join('package.json'));
console.log(path.resolve('package.json'));
console.log(path.join('src', 'index.js'));
console.log(path.resolve('src', 'index.js'));
console.log(path.join(process.cwd(), 'package.json'));
console.log(path.resolve('./', 'package.json'));
console.log(__filename);
console.log(__dirname);
然后,我们在项目 A
的根目录下执行 node scripts/index.js
,结果如下:
-> node scripts/index.js
package.json
/Users/xiaolian/Code/A/package.json
src/index.js
/Users/xiaolian/Code/A/src/index.js
/Users/xiaolian/Code/A/package.json
/Users/xiaolian/Code/A/package.json
/Users/xiaolian/Code/A/scripts/index.js
/Users/xiaolian/Code/A/scripts
品,仔细品,它们有什么区别。
个人而言,一般还是习惯用 path.join(process.cwd(), 'xxx')
。
File System 模块
// 内置模块,直接使用
const fs = require('fs');
文件系统相关操作的模块,除了 fs
之外,我们还经常用到 fs-extra
,后面会介绍。
这个模块在平时的 Node 开发中会被大量使用,这里简单列几个,其它的还是看文档哈:https://nodejs.org/dist/latest-v14.x/docs/api/fs.html[3]
fs
模块的 API 默认都是异步回调的形式,如果你想使用同步的方法,有两种解决方法:
使用 Node 提供的同步 API:
xxxSync
,也就是在 API 的后面加一个Sync
后缀,它就是一个同步方法了(具体还是需要查文档哈,是否有提供同步 API)包装成一个 Promise 使用
fs.stat(path[, options], callback)
fs.stat()
返回一个文件或者目录的信息。
const fs = require('fs');fs.stat('a.js', function(err, stats) {console.log(stats);
});
其中包含的参数有很多,介绍几个比较常用的:
export interface StatsBase<T> {isFile(): boolean; // 判断是否是一个文件isDirectory(): boolean; // 判断是否一个目录size: T; // 大小(字节数)atime: Date; // 访问时间mtime: Date; // 上次文件内容修改时间ctime: Date; // 上次文件状态改变时间birthtime: Date; // 创建时间
}
一般我们会使用 fs.stat
来取文件的大小,做一些判断逻辑,比如发布的时候可以检测文件大小是否符合规范。在 CLI 中,经常需要获取一个路径下的所有文件,这时候也需要使用 fs.stat
来判断是目录还是文件,如果是目录则继续递归。当然,现在也有更方便的 API 可以完成这个工作。
同步方法
const fs = require('fs');try {const stats = fs.statSync('a.js');
} catch(e) {}
fs.readdir(path[, options], callback)
fs.readdir(path)
获取 path
目录下的文件和目录,返回值为一个包含 file
和 directory
的数组。
假设当前目录为:
.
├── a
│ ├── a.js
│ └── b
│ └── b.js
├── index.js
└── package.json
执行以下代码:
const fs = require('fs');fs.readdir(process.cwd(), function (error, files) {if (!error) {console.log(files);}
});
返回值为:
[ 'a','index.js','package.json' ]
可以看到这里只返回了根目录下的文件和目录,并没有去深度遍历。所以如果需要获取所有文件名,就需要自己实现递归。
同步方法
const fs = require('fs');try {const dirs = fs.readdirSync(process.cwd());
} catch(e) {}
fs.readFile(path[, options], callback)
文件读取的 API,通过 fs.readFile
可以获取指定 path
的文件内容。
入参如下:
第一个参数: 文件路径
第二个参数: 配置对象,包括
encoding
和flag
,也可以直接传如encoding
字符串第三个参数: 回调函数
使用方法如下:
const fs = require('fs');
const path = require('path');fs.readFile(path.join(process.cwd(), 'package.json'), 'utf-8', function (error,content
) {if (!error) {console.log(content);}
});
如果没传 encoding
,则其默认值为 null
,此时返回的文件内容为 Buffer
格式。
同步方法
const fs = require('fs');try {fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf-8');
} catch(e) {}
fs.writeFile(file, data[, options], callback)
对应着读文件 readFile
,fs
也提供了写文件的 API writeFile
,接收四个参数:
第一个参数: 待写入的文件路径
第二个参数: 待写入的文件内容
第三个参数: 配置对象,包括
encoding
和flag
,也可以直接传如encoding
字符串第三个参数: 回调函数
使用方法如下:
const fs = require('fs');
const path = require('path');fs.writeFile(path.join(process.cwd(), 'result.js'),'console.log("Hello World")',function (error, content) {console.log(error);}
);
同步方法
const fs = require('fs');
const path = require('path');try {fs.writeFileSync(path.join(process.cwd(), 'result.js'),'console.log("Hello World")','utf-8');
} catch (e) {}
本文主要是总结了一下在开发 Node 时常用的一些 API,后续的文章会带来 Node 常用的一些三方包。
参考资料
[1]
官方文档: https://nodejs.org/dist/latest-v14.x/docs/api/
[2]DefinePlugin: https://webpack.js.org/plugins/define-plugin
[3]https://nodejs.org/dist/latest-v14.x/docs/api/fs.html: https://nodejs.org/dist/latest-v14.x/docs/api/fs.html
最近组建了一个江西人的前端交流群,如果你也是江西人可以加我微信 ruochuan12 拉你进群。
················· 若川出品 ·················
今日话题
去年清明假期开通了第二个微信号 ruochuan12,昨天突破2000好友了。清明假期打算完稿vuex4源码文章,可惜,我是没有完成,错误的预估了工作量,和自己的惰性...我可能可以封为资深拖稿专家了。
一个愿景是帮助5年内前端人走向前列的公众号
可加我个人微信 ruochuan12,长期交流学习
推荐阅读
我在阿里招前端,我该怎么帮你?(现在还能加我进模拟面试群)
如何拿下阿里巴巴 P6 的前端 Offer