常用的内置模块
专有名词说明
dirname
包含完整路径的目录名,比如 Users/project/filename
包含完整路径的文件名,比如 Users/project/index.jsbasename
只包含路径的最后一个路径片段名词,比如 index.js 或者 projectextname
表示一个文件的扩展名工作目录
表示执行当前 node 脚本代码文件的目录
path 模块
const path = require("path");
用于资源路径处理,使用该模块上的一些方法可以提取出一个路径的文件名、文件夹路径、后缀名等,也可以对路径进行拼接,并且可以处理路径中一些有歧义的字符,比如 Linux 的路径是使用斜杠,Windows 则是使用反斜杠。
path.basename(path: string, ext?: string)
path
ext
可选的文件扩展名
获取 path
最后一个路径片段名称,原理就是保留 path
的最后一个路径片段,如果传入了 ext
参数,则返回的字符串尾部会去掉 ext
。
const path = require("path");
path.basename(__dirname); // 返回 "project"
path.basename(__filename); // 返回 "index.js"
path.basename(__filename, ".js"); // 返回 "index"
path.dirname(path: string)
获取 path
的目录名,原理就是将 path
的最后一个路径片段省略。
const path = require("path");
path.dirname("./project/index.js"); // 返回 "./project"
path.dirname("./project"); // 返回 "./project"
path.extname(path: string)
获取 path
的扩展名,原理就是获取路径中最后一次出现的 .
及其以后的子串。
const path = require("path");
path.extname("index.html"); // 返回: '.html'
path.extname("index.coffee.md"); // 返回: '.md'
path.extname("index."); // 返回: '.'
path.extname("index"); // 返回: ''
path.extname(".index"); // 返回: ''
path.extname(".index.md"); // 返回: '.md'
path.isAbsolute(path: string)
判断 path
是否为一个绝对路径。
const path = require("path");
path.isAbsolute("/foo/bar"); // true
path.isAbsolute("/baz/.."); // true
path.isAbsolute("qux/"); // false
path.isAbsolute("."); // false
path.relative(from: string, to: string)
根据当前工作目录,获取从 from
目录到 to
目录的相对路径。
const path = require("path");
path.relative("/data/orandea/test/aaa", "/data/orandea/impl/bbb"); // 返回: '../../impl/bbb'
path.resolve([...paths])
将传入的多个 path
拼接为一个绝对路径,顺序是从右往左,如果在处理完所有给定的 path
片段之后,还没有生成绝对路径,则拼接上当前的工作目录。
const path = require("path");
path.resolve("/foo/bar", "./baz"); // 返回: '/foo/bar/baz'
path.resolve("/foo/bar", "/tmp/file/"); // 返回: '/tmp/file',因为顺序从右往左,第一个就是绝对路径所以直接返回了
path.resolve("wwwroot", "static_files/png/", "../gif/image.gif");
// 如果当前工作目录是 /home/myself/node,
// 则返回 '/home/myself/node/wwwroot/static_files/gif/image.gif'
因为顺序是从右往左,如果右边的某个相对路径前面是 ../
,那么左边的一个路径会退回上一级。
'../gif/image.gif' 前面是 ../
所以它左边的路径 'static_files/png/' 会返回上一级,变成 'static_files'
path.join([...paths])
用于使用特定于平台的分隔符作为定界符将所有给定的 path
片段连接在一起,然后规范化生成的路径。
const path = require("path");
path.join("/foo", "bar", "baz/asdf", "quux", ".."); // 返回: '/foo/bar/baz/asdf'
path.join("/foo", "bar", "baz/asdf", "quux"); // 返回: '/foo/bar/baz/asdf/quux'
path.parse(path)
将路径字符串转换为一个路径对象,其属性表示 path
的重要元素。
在 MacOS 上:
const path = require("path");
path.parse("/home/user/dir/file.txt");
// 返回:
// {
// root: '/',
// dir: '/home/user/dir',
// base: 'file.txt',
// ext: '.txt',
// name: 'file'
// }
在 Windows 上:
path.parse("C:\\path\\dir\\file.txt");
// 返回:
// {
// root: 'C:\\',
// dir: 'C:\\path\\dir',
// base: 'file.txt',
// ext: '.txt',
// name: 'file'
// }
path.format(pathObject)
和 path.parse
相反,它是将路径对象转换为路径字符串。
当向 pathObject
提供属性时,存在一个属性优先于另一个属性的组合:
- 如果提供
pathObject.dir
,则忽略pathObject.root
- 如果
pathObject.base
存在,则忽略pathObject.ext
和pathObject.name
在 MacOS 上:
// 如果提供 `dir`、`root` 和 `base`,
// 则将返回 `${dir}${path.sep}${base}`。
// `root` 将被忽略。
path.format({
root: "/ignored",
dir: "/home/user/dir",
base: "file.txt",
});
// 返回: '/home/user/dir/file.txt'
// 如果未指定 `dir`,则将使用 `root`。
// 如果仅提供 `root` 或 `dir` 等于 `root`,则将不包括平台分隔符。
// `ext` 将被忽略。
path.format({
root: "/",
base: "file.txt",
ext: "ignored",
});
// 返回: '/file.txt'
// 如果未指定 `base`,则将使用 `name` + `ext`。
path.format({
root: "/",
name: "file",
ext: ".txt",
});
// 返回: '/file.txt'
在 Windows 上:
path.format({
dir: "C:\\path\\dir",
base: "file.txt",
});
// 返回: 'C:\\path\\dir\\file.txt'
fs 模块
const fs = require("fs");
const fsPromise = require("fs/promises");
import * as fs from "fs";
import * as fs from "fs/promises";
该模块用于文件系统交互,所有文件系统操作 API 都具有「同步」、「异步回调」和「基于 promise」的三种形式,并且可以使用 CommonJS 语法和 ES6 模块进行访问。
- 同步操作文件:代码会被阻塞,读取文件时,后续代码不会执行
- 异步回调操作文件:代码不会被阻塞,调用 API 时一般传入一个回调函数,当获取到结果后,回调函数执行
- 基于 promise 操作文件:代码不会被阻塞,调用 API 时会返回一个 promise 对象,当获取到结果后,会执行 promise 对象上相应回调
同步调用示例
同步的 API 会阻止 Node.js 事件循环和进一步的 JavaScript 执行,直到操作完成。
操作过程中如果出现异常,那么会直接抛出异常,如果操作成功,那么会返回操作结果。
const path = require("path");
const fs = require("fs");
const filepath = "./modules/text.txt";
try {
const fileState = fs.statSync(path.resolve(__dirname, filepath));
console.log(fileState);
} catch (err) {
// 错误处理
}
异步回调示例
回调函数会作为 API 的最后一个参数,当操作得到结果后,会立刻回调该函数。
回调函数的第一个参数是异常信息(没有异常则为 undefined 或 null),第二个参数是则是操作成功后的结果。
const path = require("path");
const fs = require("fs");
const filepath = "./modules/text.txt";
fs.stat(path.resolve(__dirname, filepath), (err, res) => {
// 操作异常则会传入异常信息到回调中,需要我们手动抛出异常
if (err) throw err;
console.log(res);
});
异步回调的形式相对于 Promise 形式来说性能更好一点,但是容易出现 callback hell。
Promise 示例
调用 promises 下的 API 会返回一个 Promise 对象。
const path = require("path");
const fs = require("fs/promises");
const filepath = "./modules/text.txt";
fs.stat(path.resolve(__dirname, filepath)).then(
(res) => {
console.log(res);
},
(err) => {
// 错误处理
}
);
文件描述符(fd)
在 POSIX 系统中,对于每个进程,系统内核都维护着一张当前打开的文件和运行的资源的表格,每个打开的文件都被分配了一个 文件描述符 (fd),是一个简单的数字标识符。
在 Node.js 中,一些操作文件的 API 可以直接通过 文件描述符 去访问该文件。
const fs = require("fs");
const path = require("path");
fs.open(path.resolve(__dirname, "modules/text.txt"), (err, fd) => {
fs.fstat(fd, (err, res) => {
console.log(res);
});
});
fs.open
操作成功后,会返回目标文件的文件描述符fs.fstat
可以传入一个文件描述符来获取该文件的信息
文件系统标志(flag)
fs
模块中的一些方法可以使用如下 flag
,用于设置读写文件的模式或者权限。
a 前缀
记作 append
,用于内容追加,具体区别如下:
a
打开文件具有写入权限,如果文件不存在,则创建文件。a+
打开文件具有读写权限,如果文件不存在,则创建文件。ax
创建文件具有写入权限,如果文件存在,则操作异常。ax+
创建文件具有读写权限,如果文件存在,则操作异常。
w 前缀
记作 write
,用于内容覆盖,具体区别如下:
w
打开文件具有写入权限,如果文件不存在,则创建文件。w+
打开文件具有读写权限,如果文件不存在,则创建文件。wx
创建文件具有写入权限,如果文件存在,则操作异常。wx+
创建文件具有读写权限,如果文件存在,则操作异常。
r 前缀
记作 read
,用于内容读取,具体区别如下:
r
打开文件具有读取权限,如果文件不存在,则操作异常。r+
打开文件具有读写权限,如果文件不存在,则操作异常。
有些标志是特定于操作系统的,可能不同的操作系统对于某些标志不适用,详情参考 NodeJS —— 文件系统标志。
fs 常量(mode)
用于文件读取、写入、执行、打开、复制等权限,优先级没有 flag
高,具体参考 NodeJS —— fs 常量。
fs.stat(path[, options], callback)
用于获取一个文件各种信息(大小、创建时间、更新时间...)。
fs.access(path[, mode], callback)
用于测试一个文件或者目录的权限(是否存在、可读、可写...)。
path
文件路径或者文件目录的路径mode
可选常量,默认值为fs.constants.F_OK
callback
只有 error 参数的回调
import { access, constants } from "fs";
const file = "package.json";
// 检查当前目录中是否存在该文件。文件存在则 error 为空。
access(file, constants.F_OK, (err) => {
console.log(`${file} ${err ? "does not exist" : "exists"}`);
});
// 检查文件是否可读。
access(file, constants.R_OK, (err) => {
console.log(`${file} ${err ? "is not readable" : "is readable"}`);
});
// 检查文件是否可写。
access(file, constants.W_OK, (err) => {
console.log(`${file} ${err ? "is not writable" : "is writable"}`);
});
// 检查当前目录中是否存在文件,是否可写。
access(file, constants.F_OK | constants.W_OK, (err) => {
if (err) {
console.error(
`${file} ${err.code === "ENOENT" ? "does not exist" : "is read-only"}`
);
} else {
console.log(`${file} exists, and it is writable`);
}
});
在调用 fs.open()
、fs.readFile()
或 fs.writeFile()
之前,不要使用 fs.access()
检查文件的可访问性。
常量 | 描述 |
---|---|
F_OK | 指示文件对调用进程可见的标志。 这对于确定文件是否存在很有用,但没有说明 rwx 权限。 未指定模式时的默认值。 |
R_OK | 指示文件可以被调用进程读取的标志。 |
W_OK | 指示文件可以被调用进程写入的标志。 |
X_OK | 指示文件可以被调用进程执行的标志。 这在 Windows 上不起作用(行为类似于 fs.constants.F_OK )。 |
fs.writeFile(file, data[, options], callback)
用于向目标文件写入数据。
file
可以是一个文件路径(string),也可以是一个文件描述符(number)data
写入文件的内容options
可以是一个可选字符串表示编码,也可以是一个可选对象,有如下属性:encoding
默认值为"utf-8"
mode
默认值为0o666
(可读可写)flag
默认值为"w"
,写入的内容会覆盖原内容
callback
只有 error 参数的回调
const fs = require("fs");
const path = require("path");
fs.writeFile(
path.resolve(__dirname, "./modules/text.txt"),
"DwD sbbb",
{ flag: "a" },
(err) => {
if (err) throw err;
}
);
fs.readFile(path[, options], callback)
用于向目标文件读取内容。
path
可以是一个文件路径,也可以是一个文件描述符options
可以是一个可选字符串表示编码,也可以是一个可选对象:encoding
默认值为null
flag
默认值为r
callback
具有 error 和 res 参数的回调,如果encoding
为null
则res
是一个缓存流 buffer 对象
const fs = require("fs");
const path = require("path");
fs.readFile(
path.resolve(__dirname, "./modules/text.txt"),
"utf-8",
(err, data) => {
console.log(data);
}
);
如果我们读文件数据时指定了编码,那么返回的结果就是字符串。
fs.mkdir(path[, options], callback)
用于创建一个文件目录。
const fs = require("fs");
const path = require("path");
const dirpath = path.resolve(__dirname, "yukee798");
fs.access(dirpath), (inexist) => {
if (inexist) {
fs.mkdir(dirpath, (err) => {
// 如果文件目录存在则抛出异常
if (err) throw err;
});
}
});
fs.readdir(path[, options], callback)
用于读取一个文件目录中的内容,回调有两个参数 (err, files)
,其中 files
是目录中文件名的数组。
options
是一个可选对象,有如下属性:
encoding
默认值utf-8
withFileTypes
默认值为false
,设置为true
的话,回调函数里的files
参数就是 fs.Diren 对象
const fs = require("fs");
const path = require("path");
const dirpath = path.resolve(__dirname, "modules");
// 基本用法,只能读取一个文件目录第一层的内容
fs.readdir(dirpath, (err, res) => {
if (err) throw err;
console.log(res);
});
// 递归读取一个文件目录中的所有文件
function deepReadDir(dirpath) {
fs.readdir(
dirpath,
{
withFileTypes: true,
},
(err, files) => {
if (err) throw err;
for (let file of files) {
if (file.isDirectory()) {
deepReadDir(path.resolve(dirpath, file.name));
} else {
console.log(file.name);
}
}
}
);
}
fs.rename(oldPath, newPath, callback)
用于给一个文件或者目录进行重命名,并且可以使用该方法来移动文件或目录的位置,newPath
如果已经存则会直接覆盖旧的内容,如果 newPtah
是一个目录且里面具有文件夹或文件,则会报错。
重命名目录时:
- 可以将原目录重命名为一个没有冲突的新目录
- 可以将原目录重命名为已经存在的空目录(覆盖空目录)
- 目标路径不能是已存在的文件
重命名文件时:
- 目标路径最好是一个没有冲突的路径
- 如果路径冲突,目标路径是一个文件,则直接覆盖掉该文件,目标路径是一个目录则报错
const fs = require("fs");
const path = require("path");
fs.rename(dirpath, path.resolve(__dirname, "yukee798"), (err) => {
if (err) throw err;
});
events 模块
该模块是向外暴露出的是一个 EventEmitter
类,使用该类可以创建一个 事件触发器 实例。
这些对象暴露了 eventEmitter.on()
方法,允许将一个或多个函数绑定到对象触发的命名事件。eventEmitter.emit()
方法用于触发事件。
const EventEmitter = require("events");
const emitter = new EventEmitter();
// 监听事件
const callback1 = (args) => {
console.log("监听器回调1:", args);
};
emitter.on("fetchUserData", callback1);
const callback2 = (args) => {
console.log("监听器回调2:", args);
};
emitter.on("fetchUserData", callback2);
setTimeout(() => {
// 1s 后触发事件
emitter.emit("fetchUserData", { username: "yukee798", passward: "123456" });
}, 1000);
使用 emitter.off
可以取消一个事件监听回调。
emitter.off("fetchUserData", callback1);
使用 emitter.removeAllListeners
可以注销某几个事件所有监听器,或者所有事件的所有监听器。
emitter.removeAllListeners("click", "event"); // 注销指定的两个事件所有监听器
emitter.removeAllListeners(); // 注销所有事件所有监听器
使用 emitter.once
可以让事件监听只触发一次,然后自动注销监听器。
const myEmitter = new MyEmitter();
let m = 0;
myEmitter.once("event", () => {
console.log(++m);
});
myEmitter.emit("event");
// 打印: 1
myEmitter.emit("event");
// 忽略
使用 emitter.eventNames
可以获取到一个监听器的所注册的所有事件。
const emitter = new EventEmitter();
emitter.on("click", () => {});
emitter.on("tap", () => {});
emitter.on("doubleClick", () => {});
emitter.eventNames(); // ["click", "tap", "doubleClick"]
使用 emitter.listenerCount
可以获取监听器的某个事件有多少个回调。
使用 emitter.listeners
可以获取监听器的某个事件所有的回调函数。
默认情况下同一个事件会按照回调绑定的顺序同步执行回调,使用 emitter.prependListener
来注册事件响应回调,可以将该回调提前到最开始。