一、内置模块Path

path模块用于对路径和文件进行处理,提供了很多好用的方法。

我们知道在Mac OS、Linux和window上的路径时不一样的

  • window上会使用 \或者 \ 来作为文件路径的分隔符,当然目前也支持 /;
  • 在Mac OS、Linux的Unix操作系统上使用 / 来作为文件路径的分隔符;

如果我们在window上使用 \ 来作为分隔符开发了一个应用程序,要部署到Linux上面应该怎么办呢?

路径会出现一些问题;所以为了屏蔽他们之间的差异,在开发中对于路径的操作我们可以使用 path 模块;

  • 可移植操作系统接口(英语:Portable Operating System Interface,缩写为POSIX)
  • Linux和Mac OS都实现了POSIX接口;
  • Window部分电脑实现了POSIX接口;

show me code

1
2
3
4
5
6
const path = require('path');
const baseUrl = '/User/xy';
const fileName = 'aaa.txt';

const filePath = path.resolve(baseUrl, fileName);
console.log(filePath);
1
2
E:\project\vs  project\Node学习\04-内置模块的使用\01-path模块>node 01-路径的演练.js
E:\User\xy\aaa.txt

这样就可以根据不同操作系统进行不同的路径拼接。而不是写死路径引发一些问题。

常用API

从路径中获取信息

  • dirname:获取文件的父文件夹;
  • basename:获取文件名;
  • extname:获取文件扩展名;
1
2
3
4
5
6
7
8
9
const path = require('path');
const filePath = '/User/xy/aaa.txt';

//文件目录
console.log(path.dirname(filePath));
//文件名字
console.log(path.basename(filePath));
//文件后缀名
console.log(path.extname(filePath));
1
2
3
4
E:\project\vs  project\Node学习\04-内置模块的使用\01-path模块>node 02-path常用方法.js
/User/xy
aaa.txt
.txt

路径的拼接

  • 如果我们希望将多个路径进行拼接,但是不同的操作系统可能使用的是不同的分隔符;
  • 这个时候我们可以使用path.join函数;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const baseUrl = '../User/xy';
const fileName = 'aaa.txt';
// ..\User\xy\aaa.txt
const filePath1 = path.join(baseUrl, fileName);

const baseUrl = '/User/xy';
const fileName = 'aaa.txt';
// \User\xy\aaa.txt
const filePath1 = path.join(baseUrl, fileName);

const baseUrl = 'User/xy';
const fileName = 'aaa.txt';
// User\xy\aaa.txt
const filePath1 = path.join(baseUrl, fileName);

const baseUrl = './User/xy';
const fileName = 'aaa.txt';
// User\xy\aaa.txt
const filePath1 = path.join(baseUrl, fileName);

resolve.join()方法比较笨

将文件和某个文件夹拼接

  • 如果我们希望将某个文件和文件夹拼接,可以使用 path.resolve;
  • resolve函数会判断我们拼接的路径前面是否有 /或../或./;
  • 如果有表示是一个绝对路径,会返回对应的拼接路径;
  • 如果没有,那么会和当前执行文件所在的文件夹进行路径的拼接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/*关于path.resolve()
1、从后向前,若字符以 / 开头,不会拼接到前面的路径;

2、若以 ../ 开头,拼接前面的路径,且不含最后一节路径;

3、若连续出现多个../../..则忽略前方..个路径名进行拼接;

4、若以 ./ 开头 或者没有符号 则拼接前面路径;
*/
console.log(path.resolve('/foo/bar', '/baz')); // returns '/baz'
console.log(path.resolve('/foo/bar', './baz')); // returns '/foo/bar/baz'
console.log(path.resolve('/foo/bar', 'baz')); // returns '/foo/bar/baz'
console.log(path.resolve('/foo/bar', '../baz')); // returns '/foo/baz'

// E:\project\vs project\Node学习\04-内置模块的使用\01-path模块\home\foo\baz
console.log(path.resolve('home', '/foo/bar', '../baz')); // returns '/foo/baz'

// E:\project\vs project\Node学习\04-内置模块的使用\01-path模块\home\foo\baz
console.log(path.resolve('home', './foo/bar', '../baz')); // returns '/home/foo/baz'

// E:\project\vs project\Node学习\04-内置模块的使用\01-path模块\home\foo\asset
console.log(path.resolve('home', 'foo/bar', '../baz')); // returns '/home/foo/baz'

// E:\project\vs project\Node学习\04-内置模块的使用\01-path模块\home\asset
console.log(
path.resolve('home', 'foo', 'build', 'aaaa', 'aadada', '../../..', 'asset')
); //return '/home/foo/asset'
console.log(
path.resolve(
'home',
'foo',
'build',
'aaaa',
'aadada',
'../../../../',
'asset'
)
);
//path.resolve总是返回一个以相对于当前的工作目录(working directory)的绝对路径。

二、内置模块fs

fs是File System的缩写,表示文件系统。

  • 对于任何一个为服务器端服务的语言或者框架通常都会有自己的文件系统:
  • 因为服务器需要将各种数据、文件等放置到不同的地方;
  • 比如用户数据可能大多数是放到数据库中的(后面我们也会学习);
  • 比如某些配置文件或者用户资源(图片、音视频)都是以文件的形式存在于操作系统上的;

Node也有自己的文件系统操作模块,就是fs:

  • 借助于Node帮我们封装的文件系统,我们可以在任何的操作系统(window、Mac OS、Linux)上面直接去操作文件;
  • 这也是Node可以开发服务器的一大原因,也是它可以成为前端自动化脚本等热门工具的原因;

常用API

三种操作方式:

  • 方式一:同步操作文件:代码会被阻塞,不会继续执行;
  • 方式二:异步回调函数操作文件:代码不会被阻塞,需要传入回调函数,当获取到结果时,回调函数被执行;
  • 方式三:异步Promise操作文件:代码不会被阻塞,通过 fs.promises 调用方法操作,会返回一个Promise,可以通过then、catch进行处理

1、同步方式

aaa.txt在同目录下

1
2
3
4
5
const fs = require('fs')
//方式一 同步方式 会阻塞后续代码执行
const state = fs.statSync('./aaa.txt')
console.log(state);
console.log('我是后续代码');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
E:\project\vs  project\Node学习\04-内置模块的使用\02-fs模块>node 01-三种操作文件的方式.js
Stats {
dev: 2497463895,
mode: 33206,
nlink: 1,
uid: 0,
gid: 0,
rdev: 0,
blksize: 4096,
ino: 7881299347907997,
size: 118,
blocks: 0,
atimeMs: 1636703466159.3074,
mtimeMs: 1636703466159.3074,
ctimeMs: 1636703466159.3074,
birthtimeMs: 1636703450570.1042,
atime: 2021-11-12T07:51:06.159Z,
mtime: 2021-11-12T07:51:06.159Z,
ctime: 2021-11-12T07:51:06.159Z,
birthtime: 2021-11-12T07:50:50.570Z
}
我是后续代码

2、异步方式 容易造成回调地狱

1
2
3
4
5
6
7
8
9
//异步 不阻塞后续代码执行 容易引起回调地狱
fs.stat('./aaa.txt',(err,state) => {
if(err) {
console.log(err);
return
}
console.log(state);
})
console.log('我是后续代码');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
E:\project\vs  project\Node学习\04-内置模块的使用\02-fs模块>node 01-三种操作文件的方式.js
我是后续代码
Stats {
dev: 2497463895,
mode: 33206,
nlink: 1,
uid: 0,
gid: 0,
rdev: 0,
blksize: 4096,
ino: 7881299347907997,
size: 118,
blocks: 0,
atimeMs: 1636703466159.3074,
mtimeMs: 1636703466159.3074,
ctimeMs: 1636703466159.3074,
birthtimeMs: 1636703450570.1042,
atime: 2021-11-12T07:51:06.159Z,
mtime: 2021-11-12T07:51:06.159Z,
ctime: 2021-11-12T07:51:06.159Z,
birthtime: 2021-11-12T07:50:50.570Z
}

3、异步promise方式

1
2
3
4
5
6
7
8
9
10
//三、异步promise方式
fs.promises
.stat('./aaa.txt')
.then((state) => {
console.log(state);
})
.catch((err) => {
console.log(err);
});
console.log('我是后续代码');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
E:\project\vs  project\Node学习\04-内置模块的使用\02-fs模块>node 01-三种操作文件的方式.js
我是后续代码
Stats {
dev: 2497463895,
mode: 33206,
nlink: 1,
uid: 0,
gid: 0,
rdev: 0,
blksize: 4096,
ino: 7881299347907997,
size: 118,
blocks: 0,

E:\project\vs project\Node学习\04-内置模块的使用\02-fs模块>node 01-三种操作文件的方式.js
我是后续代码
Stats {
dev: 2497463895,
mode: 33206,
nlink: 1,
uid: 0,
gid: 0,
rdev: 0,
blksize: 4096,
ino: 7881299347907997,
size: 118,
blocks: 0,
atimeMs: 1636703466159.3074,
mtimeMs: 1636703466159.3074,
ctimeMs: 1636703466159.3074,
birthtimeMs: 1636703450570.1042,
atime: 2021-11-12T07:51:06.159Z,
mtime: 2021-11-12T07:51:06.159Z,
ctime: 2021-11-12T07:51:06.159Z,
birthtime: 2021-11-12T07:50:50.570Z
}

文件描述符

  • POSIX 系统上,对于每个进程,内核都维护着一张当前打开着的文件和资源的表格
  • 每个打开的文件都分配了一个称为文件描述符的简单的数字标识符。
  • 在系统层,所有文件系统操作都使用这些文件描述符来标识和跟踪每个特定的文件。
  • Windows 系统使用了一个虽然不同但概念上类似的机制来跟踪资源。

为了简化用户的工作,Node.js 抽象出操作系统之间的特定差异,并为所有打开的文件分配一个数字型的文件描述

符。

  • fs.open() 方法用于分配新的文件描述符。
  • 一旦被分配,则文件描述符可用于从文件读取数据、向文件写入数据、或请求关于文件的信息。
1
2
3
4
5
6
7
8
9
const fs = require('fs')
fs.open('./aaa.txt',(err,fd) => {
//3
console.log(fd);

fs.fstat(3,(err,state) => {
console.log(state);
})
})

文件的读写

fs.readFile(path[, options], callback):读取文件的内容;

fs.writeFile(file, data[, options], callback):在文件中写入内容;

文件写入

1
2
3
4
5
const fs = require('fs');
let content = '写入内容';
fs.writeFile('./aaa.txt', content, {}, (err) => {
console.log(err);
});

在上面的代码中,你会发现有一个大括号没有填写任何的内容,这个是写入时填写的option参数:flag:写入的方式

flag选项

  • flag的值有很多:https://nodejs.org/dist/latest-v14.x/docs/api/fs.html#fs_file_system_flags
  • w 打开文件写入,默认值;
  • w+打开文件进行读写,如果不存在则创建文件;
  • r+ 打开文件进行读写,如果不存在那么抛出异常;
  • r打开文件读取,读取时的默认值;
  • a打开要写入的文件,将流放在文件末尾。如果不存在则创建文件;
  • a+打开文件以进行读写,将流放在文件末尾。如果不存在则创建文件
1
2
3
4
5
6
7
const fs = require('fs');

//创建文件并写入内容
let content = '写入内容';
fs.writeFile('./aaa.txt', content, {flag:'a+'}, (err) => {
console.log(err);
});

encoding

encoding:字符的编码;

coderwhy老师简书上写过一篇关于字符编码的文章:https://www.jianshu.com/p/899e749be47c

目前基本用的都是UTF-8编码;

文件读取

不填写encoding,返回的结果是Buffer;

1
2
3
4
5
6
fs.readFile('./aaa.txt',(err,data) => {
console.log(data);
})

E:\project\vs project\Node学习\04-内置模块的使用\02-fs模块>node 03-文件的读写.js
<Buffer e5 86 99 e5 85 a5 e5 86 85 e5 ae b9>
1
2
3
4
5
6
fs.readFile('./aaa.txt', { encoding: 'utf-8' }, (err, data) => {
console.log(data);
});

E:\project\vs project\Node学习\04-内置模块的使用\02-fs模块>node 03-文件的读写.js
写入内容

三、内置模块events

Node中的核心API都是基于异步事件驱动的:

  • 在这个体系中,某些对象(发射器(Emitters))发出某一个事件;
  • 我们可以监听这个事件(监听器 Listeners),并且传入的回调函数,这个回调函数会在监听到事件时调用;
  • 发出事件和监听事件都是通过EventEmitter类来完成的,它们都属于events对象
    • **emitter.on(eventName, listener)**:监听事件,也可以使用addListener;
    • **emitter.off(eventName, listener)**:移除事件监听,也可以使用removeListener;
    • **emitter.emit(eventName[, …args])**:发出事件,可以携带一些参数;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const EventEmitter = require('events');
//创建发射器
let eventBus = new EventEmitter();

//监听事件 on是Listener的简写
eventBus.on('click', (args) => {
console.log('监听到1的click事件', args);
});
const listener2 = (args) => {
console.log('监听到2的click事件', args);
};
eventBus.on('click', listener2);

//发出事件
eventBus.emit('click', 'xxx');
//取消事件
eventBus.off('click', listener2);
eventBus.emit('click', 'yyy');
1
2
3
4
E:\project\vs  project\Node学习\04-内置模块的使用\03-events模块>node 01-events的基本使用.js
监听到1的click事件 xxx
监听到2的click事件 xxx
监听到1的click事件 yyy

常见属性

EventEmitter的实例有一些属性,可以记录一些信息:

  • emitter.eventNames():返回当前 EventEmitter对象注册的事件字符串数组;
  • emitter.getMaxListeners():返回当前 EventEmitter对象的最大监听器数量,可以通过setMaxListeners()来修改,默认是10;
  • emitter.listenerCount(事件名称):返回当前 EventEmitter对象某一个事件名称,监听器的个数;
  • emitter.listeners(事件名称):返回当前 EventEmitter对象某个事件监听器上所有的监听器数组;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const EventEmitter = require('events');
//创建发射器
let eventBus = new EventEmitter();

//监听事件 on是Listener的简写
eventBus.on('click', (args) => {
console.log('监听到1的click事件', args);
});
eventBus.on('top', (args) => {
console.log('监听到1的click事件', args);
});
const listener2 = (args) => {
console.log('监听到2的click事件', args);
};
eventBus.on('click', listener2);

//发出事件
eventBus.emit('click', 'xxx');
eventBus.emit('top', 'yyy');

console.log(eventBus.eventNames()); //[ 'click', 'top' ]
console.log(eventBus.listenerCount('click')); //2
console.log(eventBus.listeners('click')); //[ [Function], [Function: listener2] ]
console.log(eventBus.getMaxListeners('click')); //10

方法补充:

  • emitter.once(eventName, listener):事件监听一次
  • emitter.prependListener():将监听事件添加到最前面
  • emitter.prependOnceListener():将监听事件添加到最前面,但是只监听一次
  • emitter.removeAllListeners([eventName]):移除所有的监听器