Nodejs基础知识

image-20230820222142726

image-20230820222404154

image-20230820222515568

image-20230820222607128

image-20230820222704762

image-20230820222745504

image-20230820222926639

image-20230820223122337

image-20230820223955311

image-20230820224057558

image-20230820224319292

image-20230820224356932

image-20230820224409767

image-20230820224422791

image-20230820224518989

image-20230820224807553

01_Buffer(缓冲器)

1.概念

Buffer 是一个类似于数组对象 ,用于表示固定长度的字节序列

Buffer 本质是一段内存空间,专门用来处理 二进制数据

image-20230413113233879

2.特点

  1. Buffer 大小固定且无法调整
  2. Buffer 性能较好,可以直接对计算机内存进行操作
  3. 每个元素的大小为 1 字节(byte)

image-20230413113256800

上面说了Buffer大小固定却无法调整,目前学习了之后确实不知道该怎么理解,可以这样理解吗?Buffer一旦创建之后,就无法重新赋值,但是里面的元素可以改变。

Buffer可以直接对计算机内存进行操作。从第一章的学习来看,好像没有进行什么有价值的操作,也许在后面的学习中会学到,到时候可以返回来记录一下。

3.使用

3-1. 创建 Buffer

Node.js 中创建 Buffer 的方式主要如下几种:

  1. Buffer.alloc

使用alloc方法创建Buffer,会对每一个二进制位进行归零操作。

  1. Buffer.allocUnsafe

使用allocUnsafe方法创建Buffer,不会对二进制位进行归零操作,如果一段二进制位是之前的程序使用的,那么会有旧的内存数据存在。

问题:既然allocUnsafe不安全,为什么不直接使用alloc方法呢?因为allocUnsafe方法创建Buffer快一些,在没有安全方面顾虑的时候,可以使用。

  1. Buffer.from

image-20230820230025879

image-20230820230137598

image-20230820230308770

3-2. Buffer 与字符串的转化

上面讲了从字符串转为Buffer,下面讲从Buffer转为字符串。

我们可以借助toString方法将 Buffer 转为字符串

toString() 默认是按照utf-8编码方式进行转换的。

3-3 Buffer 的读写

Buffer 可以直接通过[]的方式对数据进行处理。

输出结果:

image-20240805200050867

注意:

  1. 如果修改的数值超过255,则超过 8 位数据会被舍弃。为什么?因为一个字节占8个二进制位,那么一个字节可以表示的数值是2^8=256种可能。
  2. 一个utf-8的字符 一般 占 3 个字节

 

计算机基础

计算机基本组成

image-20230821213724677

image-20230821213806538

程序运行的基本流程

image-20230821214018723

image-20230821214130794

image-20230821214236255

image-20230821214441253

进程与线程

image-20230821214504410

image-20230821214543859

image-20230821214612179

image-20230821214640188

image-20230821214726595

image-20230821214802099

image-20230821214824380

image-20230821214909434

image-20230821215114938

image-20230821215151994

如果任务管理器没有显示pid,可以在菜单栏上→右键→选择pid,就可以显示了。

02_fs 模块

fs 全称为file system,称之为文件系统,是Node.js中的内置模块,可以对计算机中的磁盘进行操作。

image-20230821215821674

不要小瞧了fs模块,前端项目,特别是桌面端的应用,和系统打交道特别多。在之前的计算机基础中已经说了,程序的启动是需要先读取文件的,这时候就是fs模块发挥作用了,不是我有限的想象中的,仅仅限于用户上传文件、下载文件、打开文件这些操作。

vscode是electron写的,但是底层用到的还是nodejs的方法。比如说:打开文件、保存文件、创建文件、文件移动和重命名、文件删除等等,就是使用fs模块。说明再复杂的软件,都是由一个个基础功能堆起来的。

本章节会介绍如下几个操作:

  1. 文件写入
  2. 文件读取
  3. 文件移动与重命名
  4. 文件删除
  5. 文件夹操作
  6. 查看资源状态

1. 文件写入

文件写入就是将数据 保存到文件 中,我们可以使用如下几个方法来实现该效果。

方法说明
writeFile异步写入
writeFileSync同步写入
appendFile / appendFileSync追加写入
createWriteStream流式写入

1-1. writeFile 异步写入

语法: fs.writeFile(file, data[, options], callback)

参数说明:

返回值: undefined

代码示例:

1-2. writeFileSync 同步写入

语法: fs.writeFileSync(file, data[, options])

参数与 fs.writeFile 大体一致,只是没有 callback 参数

返回值: undefined

代码示例:

Node.js 中的磁盘操作是由其他线程完成的,结果的处理有两种模式:

这里补充一下Node的一个重要知识点,是我在《深入浅出nodejs》中看到的:

在P71中,作者讲了Node的异常处理难点,因为nodejs中大部分API都是异步的,那么如果有2个异步API,(假设为A和B),我要确保A执行没有问题之后才能执行B,那么传统的方式都是在A、B执行的时候,外面嵌套一层try...catch,那么假设A执行时有错误了,try...catch捕获到了,但是B怎么接收到A的执行错误呢?

所以nodejs在处理异常上形成了一种约定,将异常作为回调函数的第一个实参传回,如果为空值,则表明异步调用没有异常抛出。

所以我看到的大部分nodejs的API,它们的回调函数,第一个参数都是err。

1-3. appendFile / appendFileSync 追加写入

appendFile 作用是在文件尾部追加内容,appendFile 语法与 writeFile 语法完全相同

语法:

fs.appendFile(file, data[, options], callback)

fs.appendFileSync(file, data[, options])

返回值: 二者都为 undefined

实例代码:

1-4. createWriteStream 流式写入

语法: fs.createWriteStream(path[, options])

参数说明:

返回值: Object

代码示例:

程序打开一个文件是需要消耗资源的,流式写入可以减少打开关闭文件的次数。

流式写入方式适用于 大文件写入或者频繁写入 的场景, writeFile 适合于 写入频率较低的场景

1-5 写入文件的场景

整个教程的场景介绍,只是说某些api可以做什么,供参考。

文件写入 在计算机中是一个非常常见的操作,下面的场景都用到了文件写入。

需要持久化保存数据 的时候,应该想到 文件写入

2.文件读取

文件读取顾名思义,就是通过程序从文件中取出其中的数据,我们可以使用如下几种方式:

方法说明
readFile异步读取
readFileSync同步读取
createReadStream流式读取

2-1 readFile 异步读取

语法: fs.readFile(path[, options], callback)

参数说明:

返回值: undefined

代码示例:

2-2 readFileSync 同步读取

语法: fs.readFileSync(path[, options])

参数说明:

返回值: string | Buffer

代码示例:

2-3 createReadStream 流式读取

语法:fs.createReadStream(path[, options])

参数说明:

返回值: Object

代码示例:

重点注意:

这里的 rs.on 这个on事件是怎么来的???还有里面的参数"data"和"end",是固定套路吗?

我之前搜索过答案,说是Nodejs的events模块,但是具体的我还不懂,特别是文档我不知道该怎么找,可以先在视频下方问一下老师。


2024-08-05

这个还是通过ChatGPT4来解答了,解答的非常好:

问:这里面的on函数是什么?

答:

在 Node.js 中,on 函数是用于绑定事件监听器的方法,它是基于事件驱动编程模型的一部分。具体来说,on 函数用于监听指定事件,并在事件发生时执行相应的回调函数。

在你的代码中,on 函数用于监听读取流 (Readable Stream) 的 dataend 事件。以下是详细的解释:

1. data 事件

  • 事件名称: 'data'
  • 描述: 每当有一块数据从流中读取出来时,data 事件就会被触发。默认情况下,每块数据的大小是 64KB。
  • 回调参数: chunk,表示读取到的数据块。
  • 回调功能: 打印数据块的内容和长度。

2. end 事件

  • 事件名称: 'end'
  • 描述: 当所有数据都被读取完毕时,end 事件会被触发。
  • 回调参数: 无。
  • 回调功能: 打印“读取完成”的信息。

on 函数的定义

on 函数是 EventEmitter 类的方法,Readable StreamEventEmitter 的一个实例。它的定义如下:

  • eventName 字符串,表示事件的名称。
  • listener 函数,当事件触发时会调用该函数。

使用场景

在 Node.js 中,许多对象都是事件发射器,比如 fs.createReadStream 返回的流对象。通过 on 函数,你可以很方便地处理异步事件,如文件读取、网络请求、用户输入等。

例子总结

结合你的代码,on 函数绑定了两个事件监听器,一个监听 data 事件,用于处理读取到的数据块;另一个监听 end 事件,用于处理流读取完成的情况。这使得你的程序可以异步、高效地处理文件读取操作。

问(这是我特别不理解的地方,也是文档中找不到的地方):可是我在nodejs官方文档里面并没有查到fs.createReadStream的返回值可以使用on函数绑定事件?

答:

你遇到的困惑是因为 fs.createReadStream 返回的并不是一个普通对象,而是一个 Readable Stream 对象,而 Readable Stream 继承自 EventEmitter,所以它可以使用 on 函数来绑定事件。

fs.createReadStream 的返回值

在 Node.js 的官方文档中,fs.createReadStream 函数的返回值是一个 fs.ReadStream 对象。这个对象本质上是一个 Readable Stream,而 Readable Stream 继承了 EventEmitter 类,因此它可以使用 on 函数来监听和处理事件。

image-20240805210044478

事件驱动模型

Node.js 的核心是事件驱动的异步编程模型。EventEmitter 是这个模型的基础,许多核心模块(如流、HTTP 服务器、文件系统操作等)都继承自 EventEmitter,以便支持事件监听和触发。

代码示例

让我们通过代码示例和文档链接来更清晰地理解这一点。

在上面的代码中,rs 是一个 fs.ReadStream 对象,它继承自 Readable Stream,因此它有 on 方法。

文档参考

  1. EventEmitter 文档: Node.js EventEmitter
  2. Stream 文档: Node.js Stream
  3. fs.createReadStream 文档: Node.js fs.createReadStream

fs 模块的文档中,fs.createReadStream 部分会说明它返回一个 ReadStream 对象。进一步查看 Stream 模块的文档,你会发现 ReadStream 继承自 Readable Stream,而 Readable Stream 继承自 EventEmitter,这就解释了为什么 fs.createReadStream 返回的对象可以使用 on 方法。

通过这些文档和代码示例,可以更好地理解 Node.js 中的事件驱动模型以及 fs.createReadStream 返回值的行为。

那我就查看stream里面的readable stream的文档,应该是这个:

image-20240805210645331

里面详细说明了on可以绑定哪些事件,真的是解决了我的一大疑问。

image-20240805210742908

2-4 读取文件应用场景

练习:

复制资料文件夹下的“笑看风云.mp4“。

分析:现在没有学到复制、粘贴的api,但是前面学习了读取和写入的api,就这样完成。

 

哪种方式好一些?

用流式操作好一些。为什么?因为readFile和writeFile是整读、整写,这个mp4文件是77.8MB,那么在操作时就需要占用77.8MB的内存。

但流式操作,理想状态下,是每次读取64KB的内容,然后写入,那么占用内存就只需要64KB。为什么说理想状态下是占用64KB内存呢?因为读取操作是读到内存中,而写入是写入硬盘中,读到内存中比写到硬盘中要快的多,所以可能没有写入完成,就已经读取了,但这并不会造成问题。为什么?因为读取和写入是不同的线程,只要读取的顺序是对的,那么写入的顺序就是对的。

可以使用process模块的memoryUsage()方法,来查看两种方式使用的真实内存情况。

image-20230823105957607

image-20230823110037129

可以看到,当文件很大的时候,流式操作就有明显的优势了,但是文件较小时(我测试的文件大小为几十MB时),就差不多。为什么呢?不是说流式操作理想情况下占的内存是64KB吗?怎么文件较小时就差不多了。因为内存读取文件的速度比文件写入硬盘的速度快太多了,造成读取的内容堆在了内存中。

流式操作还有一种简单方法:

pipe方法官方文档:https://nodejs.cn/api/v20/stream.html#readablepipedestination-options

image-20240805212005753

3.文件移动与重命名

在 Node.js 中,我们可以使用 renamerenameSync 来移动或重命名 文件或文件夹

语法:

fs.rename(oldPath, newPath, callback)

fs.renameSync(oldPath, newPath)

参数说明:

代码示例:

4.文件删除

在 Node.js 中,我们可以使用 unlinkunlinkSync 来删除文件

语法:

fs.unlink(path, callback)

fs.unlinkSync(path)

参数说明:

代码示例:

补充:node@14.4新增了删除方法,fs.rm(path,callback)fs.rmSync(path)

5.文件夹操作

借助 Node.js 的能力,我们可以对文件夹进行 创建读取删除 等操作。

方法说明
mkdir / mkdirSync创建文件夹
readdir / readdirSync读取文件夹
rmdir / rmdirSync删除文件夹

5-1 mkdir 创建文件夹

在 Node.js 中,我们可以使用 mkdirmkdirSync来创建文件夹

语法:

fs.mkdir(path[, options], callback)

fs.mkdirSync(path[, options])

参数说明:

示例代码:

 

5-2 readdir 读取文件夹

在 Node.js 中,我们可以使用 readdirreaddirSync 来读取文件夹

语法:

fs.readdir(path[, options], callback)

fs.readdirSync(path[, options])

参数说明:

示例代码:

image-20230823111644596

image-20230823112216936

5-3 rmdir 删除文件夹

在 Node.js 中,我们可以使用 rmdirrmdirSync 来删除文件夹

语法:

fs.rmdir(path[, options], callback)

fs.rmdirSync(path[, options])

参数说明:

示例代码:

提示:现在运行fs.rmdirfs.rmdirSync异步递归删除文件夹的方法会提示DeprecationWarning,建议使用fs.rmfs.rmSync来递归删除。

6.查看资源状态

在 Node.js 中,我们可以使用 statstatSync 来查看资源的详细信息

语法:

fs.stat(path[, options], callback)

fs.statSync(path[, options])

参数说明:

示例代码:

结果值对象结构:

7.相对路径问题

fs 模块对资源进行操作时,路径的写法有两种:

相对路径中所谓的 当前目录 ,指的是 命令行的工作目录 ,而并非是文件的所在目录。也就是执行时的工作目录可能与文件的所在目录不一致,那么写相对路径就可能造成问题。(虽然我每次都会注意命令行所在的目录,但写了相对路径,程序的鲁棒性还是不够好,有造成Bug的隐患。)

所以当命令行的工作目录与文件所在目录不一致时,会出现一些 BUG。

怎么解决呢?使用绝对路径,用到下面的__dirname

8.__dirname

__dirnamerequire 类似,都是 Node.js 环境中的'全局'变量,无需引入可以直接使用。

__dirname 保存着 当前文件所在目录的绝对路径 ,可以使用 __dirname 与文件名拼接成绝对路径。

代码示例:

使用 fs 模块的时候,尽量使用 __dirname 将路径转化为绝对路径,这样可以避免相对路径产生的Bug。

注意:__dirname返回的结果,最后面不带有/,所以如果是直接拼接字符串,则需要在具体文件或文件夹名前面加上/。但如果是使用path.resolve()来拼接,则不需要在具体文件或文件夹名前面加上/,这个方法会帮助我们处理好的,如果加上了,就会形成E://a.txt这样的形式,程序也能识别、也不会报错,但最好不要这样。

9.练习

1、文件重命名,将文件夹中文件名1到9前面加上0。

image-20230823215041923

2、需求:将test2里面的文件,1至9开头的,前面添加 0。但是缺少2开头的,需要从3开头,将数字减1,让数字能够连续。

分析:需要数字连续,所以数字大于9的文件名也要改。

image-20230824090556952

 

03_path 模块

这个模块老师只是展示了一下API的使用。

path 模块提供了 操作路径 的功能,我们将介绍如下几个较为常用的几个 API:

API说明
path.resolve拼接规范的绝对路径(常用)
path.sep获取操作系统的路径分隔符
path.parse解析路径并返回对象
path.basename获取路径的基础名称
path.dirname获取路径的目录名
path.extname获取路径的扩展名

代码示例:

执行结果如下:

image-20231109114159475

从结果可以看到,不需要都记住,只需要记住path.resolve()来拼接路径,以及path.parse()解析路径即可,解析结果是一个对象,里面什么都有。

补充:

__filenamerequire__dirname一样,都是“全局”变量。

__filename返回当前文件的绝对路径。