为什么学习Node.js?

加入你有一个网页,你有没有办法让别人都访问这个网页?

这个就是Node.js的作用了,至于它是如何让每一个人都访问到的,后面会详细展开

前端开发三大框架

Vue React Angular

image-20250609200213029

这三个项目开发的时候都离不开Node.js,是一个必学项。

所以总结来说:

1.可以让其他人访问我们编写的网页

2.为后续的框架学习打基础

Node.js是什么?

有人说:Node.js 是一门编程语言,也有人说Node.js是新版本的JavaScript? 那我们来看看官方是如何描述Node.js的:Node.js® 是一个免费、开源、跨平台的 JavaScript 运行时环境, 它让开发人员能够创建服务器 Web 应用、命令行工具和脚本。

通俗来讲:Node.js 就是一款应用程序,是一款软件,它可以运行JavaScript

Node.js 的作用

1.开发服务器应用

2.开发工具类应用

3.开发桌面端应用

我们下面来展开说一下:

1.开发服务器应用

image-20250609200702080

Node.js 就可以是在B站服务器里面的软件。
我们前面说我们学习Node.js可以让别人访问到我们的网页,我们知道网页是由HTML,CSS,JavaScript 三件套组成的。那如果我们想让别人访问我们就必须使用服务器,但是服务器只有硬件,那么还得需要软件,Node.js就可以运行在服务器端,这样就可以让别人来访问我们的网站了。

2.开发工具类应用

image-20250609201015180

3.开发桌面端应用

image-20250609201050195

这三个应用都是使用electron,而electron是基于node.js开发出来的。以后可以去学习electron就可以进行开发了

Node.js 下载和安装

官网:Node.js — 在任何地方运行 JavaScript

怎么验证安装成功了吗?

使用cmd:然后输入node -v

认识命令行工具

你有没有办法不使用鼠标来打开QQ?

我们可以使用键盘上下左右,然后按回车

还没有办法呢?

我们可以使用一些快速启动工具,比如uTools

我们还可以使用cmd (win+R)

命令的结构

思考题:

如何使用命令行工具打开谷歌浏览器并访问百度首页?

win+R 输入cmd,在控制台中输入chrome http://www.baidu.com 然后输入回车即可

chrome 是命令 ,网址后面是参数 如果后面再加http://bilibli.com 就会打开两个网页

常用命令

命令行如何查看【D:/Program Files 】里的内容?

我们可以cd(change directory) 到 cd /d D:/Program Files 然后输出dir

切换盘符直接 D: 即可

Node.js 初体验

dir /s 是查看文件夹下所有的文件,ctrl+C可以停止

如果我们在hello.js中写下这样的代码:

1
console.log('hello Node.js');

我们在控制台中操作:

1
2
D:\Note\code\nodejs>node hello.js
hello Node.js

Node.js注意点

Node.js中不能使用BOM和DOM的API

image-20250609202927856

image-20250609202936856

1.Node.js中不能使用BOM和DOM的API,可以使用console和定时器API

2.Node.js中的顶级对象为global,也可以使用globalThis 访问顶级对象

Buffer

Buffer 中文译为【缓冲区】,是一个类似于Array的对象,用于表示固定长度的字节序列

换句话说,Buffer就是一段固定长度的内存空间,用于处理二进制数据

image-20250609203343347

特点

1.Buffer 大小固定且无法调整

2.Buffer 性能较好,可以直接对计算机内存进行操作

3.每个元素的大小为 1 字节(byte)

image-20250609203455768

使用

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

1.Buffer.alloc

1
2
3
//1.alloc
let buf = Buffer.alloc(10);
console.log(buf);
1
2
PS D:\Note\code\nodejs> node .\创建buffer.js
<Buffer 00 00 00 00 00 00 00 00 00 00>

2.Buffer.allocUnsafe

1
2
3
//2.allocUnsafe 
let buf_2 = Buffer.allocUnsafe(10); //所创建的有可能会有旧数据 但是速度比alloc快
console.log(buf_2);

3.Buffer.from

1
2
3
4
5
//3.from 通过字符串创建buffer
let buf_3 = Buffer.from('hello world');
console.log(buf_3);
//通过数组创建 Buffer
let buf_4 = Buffer.from([105, 108, 111, 118, 101, 121, 111, 117]);

**Buffer与字符串的转化 **

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

1
2
let buf_4 = Buffer.from([105, 108, 111, 118, 101, 121, 111, 117]);
console.log(buf_4.toString())
1
2
PS D:\Note\code\nodejs> node .\操作buffer.js
iloveyou

【这也可以是一种表白的方式】

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

**Buffer 的读写 **

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

1
2
3
4
5
6
7
8
9
10
11
//读取
console.log(buf_3[1]);
//修改
buf_3[1] = 97;
//查看字符串结果
console.log(buf_3.toString(2));//查看2进制
//溢出
let buf = Buffer.from('hello');
buf[0] = 361;//舍弃高位数字 因为最大只能255 0001 0110 1001 => 0110 1001
//中文
let buf = Buffer.from("你好") //结果是6个字节而不是2个字节

注意:

  1. 如果修改的数值超过 255 ,则超过 8 位数据会被舍弃

  2. 一个 utf-8 的字符 一般 占 3 个字节

计算机基本组成

CPU 内存 硬盘

内存:读写速度较快,断电丢失数据

硬盘:读写速度较慢,断电不丢失数据

主板:集成电路板

显卡:负责处理视频信号,当有信息需要呈现时,需要在屏幕中呈现就会将信息传递给显卡,显卡处理好后再传递给显示器。

机箱

外设

程序运行的基本流程

操作系统

windows, linux MacOS

操作系统也是一种应用程序,用来管理和调度硬件资源

image-20250609205140863

英雄联盟是如何运行的?

image-20250609205248548

小结:

程序一般保存再在硬盘中,软件安装的过程就是将程序写入硬盘的过程。

程序在运行时会加载进入内存,然后由CPU读取并执行程序

进程和线程

进程:运行的程序

进程是程序的一次执行过程

可以通过任务管理器进行查看

线程

线程是一个进程中执行的一个执行流

一个线程是属于某个进程的

可以通过pslist -dmx [PID]

线程和进程的关系

比如说有一家蜜雪冰城的奶茶店

当开店的时候就好比是进程开始运行,有很多的员工,这里的员工就是线程

fs模块

fs : file System

这个跟硬盘相关,fs模块可以实现与硬盘的交互。例如文件的创建,删除,重命名,移动,还有文件内容的写入,读取,以及文件夹的相关操作

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

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

文件写入

文件读取

文件移动与重命名

文件删除

文件夹操作

查看资源状态

一、文件写入

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

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

1 - 1. writeFile异步写入

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

参数说明:

  • file:文件名
  • data:待写入的数据
  • options(可选):选项设置
  • callback:写入回调

返回值: undefined

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 需求:
* 新建一个文件,座右铭.txt 写入内容,三人行,则必有我师焉
*/
//1.导入fs模块
const fs = require('fs');
// 2.调用方法
fs.writeFile('./座右铭.txt', '三人行,则必有我师焉', (err) => {
if (err) {
console.log('写入失败');
} else {
console.log('写入成功');
}
})

明明右键就可以新建,为什么我们要用代码?

举个例子:

比如我们有一个网站,我们希望记录用户每次访问我们网站的时间,并且记录在一个文件中。难道安排一个人吗?

vscode 也是借助了fs这个模块

fs异步与同步

小明和小红去打篮球,小红突然说要去测试,那这个时候如果我等他一起去那就是同步,但是如果我先去它后来那就是异步。

1 - 2. writeFileSync 同步写入

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

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

返回值undefined

代码示例

1
2
3
4
5
6
7
8
9
const fs = require('fs');

try {
const data = '三人行,必有我师焉。';
const filePath = './座右铭.txt';
fs.writeFileSync(filePath, data);
} catch (e) {
console.log(e);
}

Node.js 磁盘操作模式说明

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

  • 同步处理:JavaScript 主线程会等待其他线程的执行结果,然后再继续执行主线程的代码,效率较低。
  • 异步处理:JavaScript 主线程不会等待其他线程的执行结果,直接执行后续的主线程代码,效率较好。

1 - 3. appendFile / appendFileSync 追加写入

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

语法

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

返回值:二者都为 undefined

实例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const fs = require('fs');

// appendFile 示例
const appendData = '追加的数据内容';
const appendFilePath = 'existing_file.txt';

fs.appendFile(appendFilePath, appendData, (err) => {
if (err) {
console.error('追加文件错误:', err);
return;
}
console.log('文件追加成功!');
});

// appendFileSync 示例
try {
const appendFileSyncData = '这是用同步方式追加的数据';
const appendFileSyncPath = 'another_existing_file.txt';
fs.appendFileSync(appendFileSyncPath, appendFileSyncData);
} catch (e) {
console.log('同步追加文件错误:', e);
}

1 - 4. createWriteStream 流式写入

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

参数说明

  • path:文件路径
  • options(可选):选项配置

返回值Object

代码示例

1
2
3
4
5
6
7
8
let ws = fs.createWriteStream('./观书有感.txt');

ws.write('半亩方塘一鉴开\r\n');
ws.write('天光云影共徘徊\r\n');
ws.write('问渠那得清如许\r\n');
ws.write('为有源头活水来\r\n');

ws.end();

特点说明

  • 资源消耗:程序打开一个文件是需要消耗资源的,流式写入可以减少打开关闭文件的次数。
  • 适用场景:流式写入方式适用于大文件写入或者频繁写入的场景,writeFile适合于写入频率较低的场景。

1 - 5. 写入文件的场景

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

  • 下载文件
  • 安装软件
  • 保存程序日志,如 Git
  • 编辑器保存文件
  • 视频录制

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

二、文件读取

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

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

2 - 1. readFile 异步读取

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

参数说明

  • path:文件路径
  • options:选项配置(可选)
  • callback:回调函数,第一个参数为错误对象 err(若读取无错误则为 null),第二个参数为读取到的数据 data

返回值undefined

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
// 导入 fs 模块
const fs = require('fs');

fs.readFile('./座右铭.txt', (err, data) => {
if (err) throw err;
console.log(data); // 若未指定 encoding(如未指定 'utf -8'),data 是 Buffer 类型
});

fs.readFile('./座右铭.txt', 'utf-8', (err, data) => {
if (err) throw err;
console.log(data); // 此时 data 是字符串类型
});

说明

  • 第一种 readFile 调用方式,如果不指定编码('utf -8'),dataBuffer(缓冲区)类型,它是一个类似数组的对象,用于表示二进制数据。
  • 第二种 readFile 调用方式,指定了编码 'utf -8'data 会被自动转换为字符串类型,方便直接处理文本内容。

2 - 2. readFileSync 同步读取

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

参数说明

  • path:文件路径
  • options:选项配置(可选)

返回值:读取到的数据(是 Buffer 类型或者根据指定编码转换后的字符串类型)

代码示例

1
2
3
4
5
6
7
8
9
10
11
const fs = require('fs');

try {
let data1 = fs.readFileSync('./座右铭.txt');
console.log(data1); // 未指定 encoding,是 Buffer 类型

let data2 = fs.readFileSync('./座右铭.txt', 'utf -8');
console.log(data2); // 指定了 'utf -8',是字符串类型
} catch (err) {
console.log(err);
}

说明

  • readFileSync 是同步读取文件,即 JavaScript 主线程会等待文件读取完成后才继续执行后续代码。
  • 它的返回值直接是读取到的数据,不像 readFile 通过回调函数获取数据。

2 - 3. createReadStream 流式读取

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

参数说明

  • path:文件路径
  • options:选项配置(可选)

返回值ReadStream 对象

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const fs = require('fs');

const readStream = fs.createReadStream('./较大的文本文件.txt', 'utf -8'); // 假设是一个较大的文本文件

readStream.on('data', (chunk) => { // 'data' 事件,当有数据可读时触发
console.log('读取到数据块:', chunk);
});

readStream.on('end', () => { // 'end' 事件,当读取到文件末尾时触发
console.log('文件读取结束');
});

readStream.on('error', (err) => { // 'error' 事件,当读取发生错误时触发
console.log('文件读取错误:', err);
});

说明

  • 流式读取适合读取大文件,它不会一次性将整个文件读入内存,而是分块读取(chunk 表示读取到的数据块)。
  • 通过监听 ReadStream 对象的 'data''end''error' 等事件来处理读取过程中的不同情况。

2 - 4读取文件应用场景

  • 电脑开机
  • 程序运行
  • 编辑器打开文件
  • 查看图片
  • 播放视频
  • 播放音乐
  • Git查看日志
  • 上传文件
  • 查看聊天记录

复制文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 需求:复制【座右铭.txt】
*/
const fs = require('fs')
const process = require('process')
//方式一readFile
//读取文件内容
// let data = fs.readFileSync('座右铭.txt');
// //写入文件
// fs.writeFileSync('座右铭1.txt', data);
console.log(process.memoryUsage());//rss
//方式二readStream
let rs = fs.createReadStream('座右铭.txt');
let ws = fs.createWriteStream('座右铭2.txt');
rs.pipe(ws);//这个pipe管道 这个用的不是特别多知道什么意思即可
rs.on('end', () => {
console.log('复制完成');
console.log(process.memoryUsage());
})

三、文件移动与重命名

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

语法:

  • fs.rename(oldPath, newPath, callback)
  • fs.renameSync(oldPath, newPath)

参数说明:

  • oldPath:文件当前的路径
  • newPath:文件新的路径
  • callback:操作后的回调

代码示例:

1
2
3
4
5
6
fs.rename('../观书有感.txt', '../论语/观书有感.txt', (err) =>{
if(err) throw err;
console.log('移动完成')
});

fs.renameSync('../座右铭.txt', '../论语/我的座右铭.txt');

四、文件删除

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

语法

  • fs.unlink(path, callback)
  • fs.unlinkSync(path)
  • 也可以使用rm rmSync

参数说明

  • path:文件路径
  • callback:操作后的回调

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 异步删除文件示例
const fs = require('fs');

const filePath = './要删除的文件.txt';
fs.unlink(filePath, (err) => {
if (err) {
console.error('文件删除错误:', err);
return;
}
console.log('文件删除成功!');
});

// 同步删除文件示例
try {
const syncFilePath = './另一个要删除的文件.txt';
fs.unlinkSync(syncFilePath);
} catch (e) {
console.log('同步删除文件错误:', e);
}

五、文件夹操作

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

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

5 - 1 mkdir 创建文件夹

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

语法

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

参数说明

  • path:文件夹路径
  • options:选项配置(可选)
  • callback:操作后的回调

示例代码

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
40
41
42
43
44
45
// 异步创建文件夹
const fs = require('fs');

const folderPath = './new_folder';
fs.mkdir(folderPath, (err) => {
if (err) {
console.error('创建文件夹错误:', err);
return;
}
console.log('文件夹创建成功!');
});

// 同步创建文件夹
try {
const syncFolderPath = './another_new_folder';
fs.mkdirSync(syncFolderPath);
} catch (e) {
console.log('同步创建文件夹错误:', e);
}
//递归创建
const folderPath = './a/b/c';
fs.mkdir(folderPath, {recursive: true},(err) => {
if (err) {
console.error('创建文件夹错误:', err);
return;
}
console.log('文件夹创建成功!');
});

//2.3读取文件夹 read dir
fs.readdir('./资源',(err,data)=>{
if(err){
console.log('读取失败');
return;
}
console.log(data);
});
//2-4删除文件夹
fs.rmdir('./html',err=>{
if(err){
console.log('删除失败');
return;
}
console.log('删除成功')
});

5 - 2 readdir 读取文件夹

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

语法:

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

参数说明:

  • path:文件夹路径
  • options:选项配置(可选)
  • callback:操作后的回调

示例代码:

1
2
3
4
5
6
7
8
9
// 异步读取
fs.readdir('../论语/', (err, data) => {
if (err) throw err;
console.log(data);
});

// 同步读取
let data = fs.readdirSync('../论语/');
console.log(data);

5 - 3 rndir 删除文件夹

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

语法:

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

参数说明:

  • path:文件夹路径
  • options:选项配置(可选)
  • callback:操作后的回调

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//异步删除文件夹
fs.rmdir('./page', err => {
if(err) throw err;
console.log('删除成功');
});
//异步递归删除文件夹
fs.rmdir('./1', {recursive: true}, err => {
if(err) {
console.log(err);
}
console.log('递归删除')
});
//同步递归删除文件夹
fs.rmdirSync('./x', {recursive: true})

注意:

  • rmdirrmdirSync 只能删除空文件夹,如果要删除非空文件夹,需要先删除文件夹内的所有文件和子文件夹。
  • 在实际应用中,为了确保安全,建议在删除文件夹之前先确认其内容是否为预期,避免误删重要数据。

六、查看资源状态

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

语法:

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

参数说明:

  • path:文件夹路径
  • options:选项配置(可选)
  • callback:操作后的回调

示例代码:

1
2
3
4
5
6
7
8
// 异步获取状态
fs.stat('./data.txt', (err, data) => {
if (err) throw err;
console.log(data);
});

// 同步获取状态
let data = fs.statSync('./data.txt');

说明:

stat 方法返回的 data 对象包含了文件或文件夹的各种信息,例如:

  • data.isFile():判断是否为文件
  • data.isDirectory():判断是否为目录(文件夹)
  • data.size:文件大小(字节数)
  • data.birthtime:创建时间
  • data.mtime:最后修改时间等。

通过这些信息可以更深入地了解文件或文件夹的属性和状态,以便进行相应的处理操作。

结果值对象结构:

  • size:文件体积
  • birthtime:创建时间
  • mtime:最后修改时间
  • isFile:检测是否为文件
  • isDirectory:检测是否为文件夹

七、相对路径问题

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

  • 相对路径
    • ./座右铭.txt :当前目录下的座右铭.txt
    • 座右铭.txt :等同于上面的写法
    • ../座右铭.txt :当前目录的上一级目录中的座右铭.txt
  • 绝对路径
    • D:/Program Files :windows 系统下的绝对路径
    • /usr/bin :Linux 系统下的绝对路径

相对路径中所谓的 当前目录 ,指的是 命令行的工作目录 ,而并非是文件的所在目录。所以当命令行的工作目录与文件所在目录不一致时,会出现一些 BUG。

八、__dirname

  • __dirnamerequire 类似,都是 Node.js 环境中的“全局”变量。
  • __dirname 保存着当前文件所在目录的绝对路径,可以使用 __dirname 与文件名拼接成绝对路径。

代码示例:

1
2
let data = fs.readFileSync(__dirname + '/data.txt');
console.log(data);
  • 使用 fs 模块的时候,尽量使用 __dirname 将路径转化为绝对路径,这样可以避免相对路径产生的 Bug

练习:批量重命名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//导入fs
const fs = require('fs')

//读取code文件夹
const files = fs.readdirSync('./code')

//遍历数组
files.forEach(item => {
//拆分文件名
let data = item.split('-');
let [num, name] = data;
//判断
if (Number(num) < 10) {
num = '0' + num;
}
//创建新的文件名
let newName = num + '-' + name;
console.log(newName);
//重命名
fs.renameSync(`./code/${item}`, `./code/${newName}`);
})

如果把02删除了 能不能把03变02,04变03?

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 fs = require('fs');

// 读取 code 文件夹
const files = fs.readdirSync('./code');

// 按照文件名中的序号进行排序(假设文件名格式类似 "01 - something")
files.sort((a, b) => {
const numA = parseInt(a.split('-')[0]);
const numB = parseInt(b.split('-')[0]);
return numA - numB;
});

// 重新命名文件
files.forEach((item, index) => {
let newNum = index + 1; // 新的序号从 1 开始
// 补零处理
if (newNum < 10) {
newNum = '0' + newNum;
}
let data = item.split('-');
let [oldNum, name] = data;
let newName = newNum + '-' + name;
console.log(`重命名:${item} -> ${newName}`);
fs.renameSync(`./code/${item}`, `./code/${newName}`);
});

path 模块

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

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

代码示例:

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 path = require('path');

//resolve
console.log(path.resolve(__dirname,'index.html'))



// 获取路径分隔符
console.log(path.sep);//window \ linux /

// 拼接绝对路径
console.log(path.resolve(__dirname, 'test'));

// 解析路径
let pathname = 'D:/program file/nodejs/node.exe';
console.log(path.parse(pathname));

// 获取路径基础名称
console.log(path.basename(pathname))

// 获取路径的目录名
console.log(path.dirname(pathname));

// 获取路径的扩展名
console.log(path.extname(pathname));

HTTPS协议

一、概念

HTTP(hypertext transport protocoI)协议;中文叫超文本传输协议。 是一种基于 TCP/IP 的应用层通信协议。 这个协议详细规定了浏览器和万维网服务器之间互相通信的规则。 协议中主要规定了两个方面的内容:

  • 客户端:用来向服务器发送数据,可以被称之为请求报文。
  • 服务端:向客户端返回数据,可以被称之为响应报文。

报文:可以简单理解为就是一堆字符串。

【离婚协议,保密协议 https协议就是浏览器和服务器之间的协议】

如果我们想看到请求和响应的报文我们要下载一个软件叫fiddler

image-20250609221641878

二、请求报文的组成

  • 请求行
  • 请求头
  • 空行
  • 请求体

三、HTTP 的请求行

  • 请求方法(get、post、put、delete等)

  • 请求 URL(统一资源定位器)

  • HTTP 协议版本号

查询字符串和路径有什么区别?比如我要去买煎饼果子,这个就是路径,我说我煎饼果子要加辣椒要加根肠那就查询字符串

image-20250609222915592

四、HTTP 请求头

格式:『头名:头值』 常见的请求头有:

请求头 解释
Host 主机名
Connection 连接的设置 keep - alive(保持连接);close(关闭连接)
Cache - Control 缓存控制 max - age = 0(没有缓存)
Upgrade - Insecure - Requests 将网页中的 http 请求转化为 https 请求(很少用)老网站升级
User - Agent 用户代理,客户端字符串标识,服务器可以通过这个标识来识别这个请求来自哪个客户端,一般在 PC 端和手机端的区分
Accept 设置浏览器接收的数据类型
Accept - Encoding 设置接收的压缩方式
Accept - Language 设置接收的语言 q = 0.7 为喜好系数,满分为 1
Cookie 后面单独讲

五、HTTP 的请求体

请求体内容的格式是非常灵活的, (可以是空)==> GET 请求, (也可以是字符串,还可以是 JSON)===> POST 请求 例如:

  • 字符串:keywords=手机&price=2000
  • JSON:{"keywords":"手机","price":2000}

六、响应报文的组成

  • 响应行
1
HTTP/1.1 200 OK

响应状态码和响应字符串关系是一一对应的。

  • 响应头
响应头 解释
Cache - Control 缓存控制 (private 私有,只允许客户端缓存数据)
Connection 链接设置
Content - Type 设置响应体的数据类型以及字符集(如:text/html;charset=utf - 8 ,表示响应体为 html,字符集 utf - 8)
Content - Length 响应体的长度,单位为字节
  • 空行
  • 响应体 响应体内容的类型是非常灵活的,常见的类型有 HTML、CSS、JS、图片、JSON

七、创建 HTTP 服务

地址 ==》 寻找收件人

IP ==》 寻找网络设备

IP也称为【IP地址】,本身是一个数字标识 例如 192.168.1.3

IP 用来标识网络中的设备,实现设备间通信

image-20250610001641463

IP的分类

32Bit IPV4 已经不够

如何解决?

共享IP

区域共享 家庭共享

局域网IP(又称之为私网IP)在这个网络里面我们的设备是可以相互通信的,比如手机可以给电脑发一个文件,电脑可以发一个视频给手机,这些都是可以的。但是如果你想跟小伙伴开黑,打视频那这个是做不到的。必须连接互联网,去通信公司办理业务,国内主要有三家电信,联通,移动。师傅会上门帮你的路由器接一根线,然后你的路由器就会得到广域网IP或公网IP,我们说所的共享IP其实是公网IP

本地回环IP地址

127.0.0.1 其实就是本机

127.0.0.1~127.255.255.254

image-20250610083606917

IP标准分类 : https://zhuanlan.zhihu.com/p/193729352

端口

端口举例

image-20250610083823911

应用程序的数字标识

一台现代计算机有65536 个端口(0~65535)

一个应用程序可以使用一个或多个端口

端口的主要作用:实现不同主机应用程序之间的通信

身份转换~ 到后端啦

使用 nodejs 创建 HTTP 服务

7.1 操作步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 1. 导入 http 模块
const http = require('http');

// 2. 创建服务对象 create 创建 server 服务
// request 意为请求,是对请求报文的封装对象,通过 request 对象可以获得请求报文的数据
// response 意为响应,是对响应报文的封装对象,通过 response 对象可以设置响应报文
const server = http.createServer((request, response) => {
response.end('Hello HTTP server');
});

// 3. 监听端口,启动服务
server.listen(9000, () => {
console.log('服务已经启动,端口 9000 监听中...');
});

7.2测试

浏览器请求对应端口
http://127.0.0.1:9000

7.3注意事项

1.命令行ctr1+c停止服务
2.当服务启动后,更新代码必须重启服务才能生效
3.响应内容中文乱码的解决办法

1
response.setHeader('content-type','text/html;charset=utf-8');

4.端口号被占用

1
Error:listen EADDRINUSE:address already in use 9000

1)关闭当前正在运行监听端口的服务(使用较多)
2)修改其他端口号

HTTP协议默认端口

  • HTTP协议默认端口是80。
  • HTTPS协议的默认端口是443。
  • HTTP服务开发常用端口有3000、8080、8090、9000等。

端口被占用处理

如果端口被其他程序占用,可以使用资源监视器找到占用端口的程序,然后使用任务管理器关闭对应的程序。

八、浏览器查看HTTP报文

f12

九、获取HTTP请求报文

想要获取请求的数据,需要通过request对象

含义 语法 重点掌握
请求方法 request.method *
请求版本 request.httpVersion
请求路径 request.url *
URL 路径 require(‘url’).parse(request.url).pathname *
URL 查询字符串 require(‘url’).parse(request.url, true).query *
请求头 request.headers *
请求体 request.on(‘data’, function(chunk){})request.on(‘end’, function(){});

注意事项:

  1. request.url 只能获取路径以及查询字符串,无法获取 URL 中的域名以及协议的内容
  2. request.headers 将请求信息转化成一个对象,并将属性名都转化成了『小写』
  3. 关于路径:如果访问网站的时候,只填写了 IP 地址或者是域名信息,此时请求的路径为『 / 』
  4. 关于 favicon.ico:这个请求是属于浏览器自动发送的请求

9.1 练习

按照以下要求搭建 HTTP 服务

请求类型(方法) 请求地址 响应体结果
get /login 登录页面
get /reg 注册页面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//1、引入http模块
const http = require("http");
//2、建立服务
const server = http.createServer((request, response)=>{
let {url,method} = request; //对象的解构赋值
//设置响应头信息
//解决中文乱码
response.setHeader("Content-Type","text/html;charset=utf-8")
if(url == "/register" && method == "GET"){
response.end("注册页面");
}else if(url=="/login" && method == "GET"){
response.end("登录页面");
}else{
response.end("<h1>404 Not Found</h1>")
}
})

//3、监听端口
server.listen(8000,()=>{
console.log('服务启动中....');
})

十、设置 HTTP 响应报文

作用 语法
设置响应状态码 response.statusCode
设置响应状态描述 response.statusMessage(用的非常少)
设置响应头信息 response.setHeader('头名', '头值')
设置响应体 response.write('xx')``response.end('xxx')

writeend 的两种使用情况:

1. writeend 的结合使用(响应体相对分散)

1
2
3
4
response.write('xx');
response.write('xx');
response.write('xx');
response.end(); // 每一个请求,在处理的时候必须要执行 end 方法的

2. 单独使用 end 方法(响应体相对集中)

1
response.end('xxx');

10.1 练习

搭建 HTTP 服务,响应一个 4 行 3 列的表格,并且要求表格有隔行换色效果,且点击单元格能高亮显示

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
//导入 http 模块
const http = require('http');

//创建服务对象
const server = http.createServer((request, response) => {
response.end(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF - 8">
<meta http - equiv="X - UA - Compatible" content="IE = edge">
<meta name="viewport" content="width = device - width, initial - scale = 1.0">
<title>Document</title>
<style>
td{
padding: 20px 40px;
}
table tr:nth-child(odd){
background: #819dc0;
}
table tr:nth-child(even){
background: #f5de19;
}
table, td{
border: 1px solid #000;
border-collapse: collapse;
}
</style>
</head>
<body>
<table border="1">
<tr>
<td></td><td></td><td></td>
</tr>
<tr>
<td></td><td></td><td></td>
</tr>
<tr>
<td></td><td></td><td></td>
</tr>
<tr>
<td></td><td></td><td></td>
</tr>
</table>
<script>
//获取所有的 td
let tds = document.querySelectorAll('td');
//遍历
tds.forEach(item => {
item.onclick = function () {
this.style.background = '#222';
}
})
</script>
</body>
</html>
`);
});

//监听端口
server.listen(8080, () => {
console.log('服务已经启动....')
});

核心代码解释:

  • 在 HTML 表格标签<table>中,通过nth - child(odd)nth - child(even)实现了表格的隔行换色效果。
  • <script>标签中,通过document.querySelectorAll('td')获取到了所有的<td>标签元素,并通过遍历遍历为每个td元素添加了onclick事件。
  • onclick事件中,通过this.style.background = '#222'实现了点击单元格时的高亮显示效果。

HTTP 服务搭建核心逻辑:

  • 首先导入http模块。
  • 然后通过http.createServer创建了一个 HTTP 服务,在这个服务的回调函数中,返回了包含上述 HTML 代码的响应体。
  • 最后通过server.listen监听了8080端口,并在服务启动时打印了提示信息。

优化:

有没有办法解决在这里面写html代码有高亮和提示?

table.html

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
40
41
42
43
44
45
46
47
48
49
50
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF - 8">
<meta http - equiv="X - UA - Compatible" content="IE = edge">
<meta name="viewport" content="width = device - width, initial - scale = 1.0">
<title>Document</title>
<style>
td{
padding: 20px 40px;
}
table tr:nth-child(odd){
background: #819dc0;
}
table tr:nth-child(even){
background: #f5de19;
}
table, td{
border: 1px solid #000;
border-collapse: collapse;
}
</style>
</head>
<body>
<table border="1">
<tr>
<td></td><td></td><td></td>
</tr>
<tr>
<td></td><td></td><td></td>
</tr>
<tr>
<td></td><td></td><td></td>
</tr>
<tr>
<td></td><td></td><td></td>
</tr>
</table>
<script>
//获取所有的 td
let tds = document.querySelectorAll('td');
//遍历
tds.forEach(item => {
item.onclick = function () {
this.style.background = '#222';
}
})
</script>
</body>
</html>

node.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//导入 http 模块
const http = require('http');
const fs = require('fs');
//创建服务对象
const server = http.createServer((request, response) => {
//读取文件内容
let html = fs.readFileSync(__dirname + '/table.html', 'utf-8');
response.end(html);
});

//监听端口
server.listen(8080, () => {
console.log('服务已经启动....')
});

十一、网页资源的基本加载过程

网页资源的加载都是循序渐进的,首先获取 HTML 的内容, 然后解析 HTML 在发送其他资源的请求,如 CSS,Javascript,图片等。 理解了这个内容对于后续的学习与成长有非常大的帮助

如果要对上面哪个练习进行进一步的拆分:

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
//导入 http 模块
const http = require('http');
const fs = require('fs');

//创建服务对象
const server = http.createServer((request, response) => {
//获取请求url的路径
let {pathname} = new URL(request.url, 'http://127.0.0.1');
if(pathname === '/'){
//读取文件内容
let html = fs.readFileSync(__dirname + '/10_table.html');
response.end(html); //设置响应体
}else if(pathname === '/index.css'){
//读取文件内容
let css = fs.readFileSync(__dirname + '/index.css');
response.end(css); //设置响应体
}else if(pathname === '/index.js'){
//读取文件内容
let js = fs.readFileSync(__dirname + '/index.js');
response.end(js); //设置响应体
}else{
response.statusCode = 404;
response.end('<h1>404 Not Found</h1>')
}

});

//监听端口, 启动服务
server.listen(9000, () => {
console.log('服务已经启动....')
});

十二、静态资源服务

静态资源是指 内容长时间不发生改变的资源 ,例如图片,视频,CSS 文件,JS文件,HTML文件,字体文 件等

动态资源是指 内容经常更新的资源 ,例如百度首页,网易首页,京东搜索列表页面等

**12.1网站根目录或静态资源目录 **

HTTP 服务在哪个文件夹中寻找静态资源,那个文件夹就是 静态资源目录 ,也称之为 网站根目录

思考:vscode 中使用 live-server 访问 HTML 时, 它启动的服务中网站根目录是谁?

以打开文件夹

12.2 网页中的 URL

网页中的 URL 主要分为两大类:相对路径与绝对路径

12.2.1 绝对路径

绝对路径可靠性强,而且相对容易理解,在项目中运用较多

形式 特点
http://yjy.com/web 直接向目标资源发送请求,容易理解。网站的外链会用到此形式
//yjy.com/web 与页面 URL 的协议拼接形成完整 URL 再发送请求。大型网站用的比较多
/web 与页面 URL 的协议、主机名、端口拼接形成完整 URL 再发送请求。中小型网站

12.2.2 相对路径

相对路径在发送请求时,需要与当前页面 URL 路径进行 计算,得到完整 URL 后,再发送请求,学习阶段用的较多

例如当前网页 url 为 http://www.yjy.com/course/h5.html

形式 最终的 URL
.css/app.css http://www.yjy.com/course/css/app.css
js/app.js http://www.yjy.com/course/js/app.js
../img/logo.png http://www.yjy.com/img/logo.png
../../mp4/show.mp4 http://www.yjy.com/mp4/show.mp4

12.2.3 网页中使用 URL 的场景小结

包括但不限于如下场景:

  • a 标签 href
  • link 标签 href
  • script 标签 src
  • img 标签 src
  • video audio 标签 src
  • form 中的 action
  • AJAX 请求中的 URL

12.3 设置资源类型(mime类型)

媒体类型(通常称为 Multipurpose Internet Mail Extensions 或 MIME 类型)是一种标准,用来表示文档、文件或字节流的性质和格式。

mime 类型结构: [type]/[subType]

例如: text/html text/css image/jpeg image/png application/json

HTTP 服务可以设置响应头 Content-Type 来表明响应体的 MIME 类型,浏览器会根据该类型决定如何处理资源

下面是常见文件对应的 mime 类型

1
2
3
4
5
6
7
8
9
html: 'text/html',
css: 'text/css',
js: 'text/javascript',
png: 'image/png',
jpg: 'image/jpeg',
gif: 'image/gif',
mp4: 'video/mp4',
mp3: 'audio/mpeg',
json: 'application/json'
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/**
* 创建一个 HTTP 服务,端口为 9000,满足如下需求
* GET /index.html 响应 page/index.html 的文件内容
* GET /css/app.css 响应 page/css/app.css 的文件内容
* GET /images/logo.png 响应 page/images/logo.png 的文件内容
*/
//导入 http 模块
const http = require('http');
const fs = require('fs');
const path = require('path');
//声明一个变量
let mimes = {
html: 'text/html',
css: 'text/css',
js: 'text/javascript',
png: 'image/png',
jpg: 'image/jpeg',
gif: 'image/gif',
mp4: 'video/mp4',
mp3: 'audio/mpeg',
json: 'application/json'
}

//创建服务对象
const server = http.createServer((request, response) => {
if(request.method !== 'GET'){
response.statusCode = 405;
response.end('<h1>405 Method Not Allowed</h1>');
return;
}
//获取请求url的路径
let {pathname} = new URL(request.url, 'http://127.0.0.1');
//声明一个变量
let root = __dirname + '/page';
// let root = __dirname + '/../';
//拼接文件路径
let filePath = root + pathname;
//读取文件 fs 异步 API
fs.readFile(filePath, (err, data) => {
if(err){
console.log(err);
//设置字符集
response.setHeader('content-type','text/html;charset=utf-8');
//判断错误的代号
switch(err.code){
case 'ENOENT':
response.statusCode = 404;
response.end('<h1>404 Not Found</h1>');
case 'EPERM':
response.statusCode = 403;
response.end('<h1>403 Forbidden</h1>');
default:
response.statusCode = 500;
response.end('<h1>Internal Server Error</h1>');
}

return;
}
//获取文件的后缀名
let ext = path.extname(filePath).slice(1);
//获取对应的类型
let type = mimes[ext];
if(type){
//匹配到了 text/html;charset=utf-8
if(ext === 'html'){
response.setHeader('content-type', type + ';charset=utf-8');
}else{
response.setHeader('content-type', type);
}
}else{
//没有匹配到
response.setHeader('content-type', 'application/octet-stream');
}
//响应文件内容
response.end(data);
})

});

//监听端口, 启动服务
server.listen(9000, () => {
console.log('服务已经启动....')
});


对于未知的资源类型,可以选择 application/octet-stream 类型,浏览器在遇到该类型的响应时,会对响应体内容进行独立存储,也就是我们常见的 下载 效果

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
require('http').createServer((request, response) => {
// 获取请求的方法以及路径
let {url, method} = request;
// 判断请求方式以及请求路径
if (method === "GET" && url === "/index.html") {
// 需要响应文件中的内容
let data = require('fs').readFileSync(__dirname + '/index.html');
response.end(data);
} else if (method === "GET" && url === "/css/app.css") {
// 需要响应文件中的内容
let data = require('fs').readFileSync(__dirname + '/public/css/app.css');
// 这里缺少设置Content-Type响应头
response.setHeader('Content-Type', 'text/css');
response.end(data);
} else if (method === "GET" && url === "/js/app.js") {
// 需要响应文件中的内容
let data = require('fs').readFileSync(__dirname + '/public/js/app.js');
// 这里缺少设置Content-Type响应头
response.setHeader('Content-Type', 'text/javascript');
response.end(data);
} else {
// 404响应
response.statusCode = 404;
response.end("<h1>404 Not Found</h1>");
}
}).listen(80, () => {
console.log('80端口正在启动中....');
});

改进 HTTP 服务器代码(文件路径封装)

原始问题:之前的代码每增加一个请求路径就需要进行单独判断,不够灵活和完善。

改进思路:将文件路径相关逻辑进行封装,实现通用的文件请求处理。

改进代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const http = require('http');
const fs = require('fs');

http.createServer((request, response) => {
// 获取请求的方法已经路径
let { url, method } = request;
// 文件夹路径
let rootDir = __dirname + '/public';
// 拼接文件路径
let filePath = rootDir + url;
// 读取文件内容
fs.readFile(filePath, (err, data) => {
if (err) {
// 如果出现错误,响应 404 状态码
response.statusCode = 404;
response.end('<h1>404 Not Found</h1>');
} else {
// 响应文件内容
response.end(data);
}
})
}).listen(80, () => {
console.log('80 端口正在启动中....');
});

12.4 GET 和 POST 请求场景小结

GET 请求的情况:

  • 在地址栏直接输入 url 访问
  • 点击 a 链接
  • link 标签引入 css
  • script 标签引入 js
  • img 标签引入图片
  • form 标签中的 method 为 get (不区分大小写)
  • ajax 中的 get 请求

POST 请求的情况:

  • form 标签中的 method 为 post (不区分大小写)
  • AJAX 的 post 请求

十三、GET 和 POST 请求的区别

GET 和 POST 是 HTTP 协议请求的两种方式。

  • 作用:GET 主要用来获取数据,POST 主要用来提交数据
  • 参数位置:GET 带参数请求是将参数缀到 URL 之后,在地址栏中输入 url 访问网站就是 GET 请求 POST 带参数请求是将参数放到请求体中
  • 安全性:POST 请求相对 GET 安全一些,因为在浏览器中参数会暴露在地址栏
  • GET 请求大小有限制,一般为 2K,而 POST 请求则没有大小限制

Node.js 模块化

一、介绍

  1. 什么是模块化与模块? 将一个复杂的程序文件依据一定规则 (规范) 拆分成多个文件的过程称之为 模块化

其中拆分出的 每个文件就是一个模块,模块的内部数据是私有的,不过模块可以暴露内部数据以便其他模块使用

  1. 什么是模块化项目? 编码时是按照模块一个一个编码的,整个项目就是一个模块化的项目
  2. 模块化好处 下面是模块化的一些好处:
    • 防止命名冲突
    • 高复用性
    • 高维护性

二、模块暴露数据

2.1 模块初体验

可以通过下面的操作步骤,快速体验模块化:

  1. 创建 me.js
1
2
3
4
5
6
7
//声明函数
function tiemo() {
console.log('贴膜....');
}

//暴露数据
module.exports = tiemo;
  1. 创建 index.js
1
2
3
4
//导入模块
const tiemo = require('./me.js');
//调用函数
tiemo();

2.2 暴露数据

模块暴露数据的方式有两种:

  1. module.exports = value
  2. exports.name = value

使用时有几点注意:

  • module.exports 可以暴露 任意 数据
  • 不能使用 exports = value 的形式暴露数据,模块内部 moduleexports 的隐式关系 exports = module.exports = {}require 返回的是目标模块中 module.exports 的值

image-20250610110144642

三、导入(引入)模块

在模块中使用 require 传入文件路径即可引入文件

1
const test = require('./me.js');

require 使用的一些注意事项:

  1. 对于自己创建的模块,导入时路径建议写 相对路径 ,且不能省略 ./../
  2. jsjson 文件导入时可以不用写后缀,c/c++ 编写的 node 扩展文件也可以不写后缀,但是一般用不到
  3. 如果导入其他类型的文件,会以 js 文件进行处理
  4. 如果导入的路径是个文件夹,则会 首先 检测该文件夹下 package.json 文件中 main 属性对应的文件,

如果存在则导入,反之如果文件不存在会报错。

如果 main 属性不存在,或者 package.json 不存在,则会尝试导入文件夹下的 index.jsindex.json

如果还是没找到,就会报错 5. 导入 node.js 内置模块时,直接 require 模块的名字即可,无需加 ./../

四、导入模块的基本流程

这里我们介绍一下 require 导入 自定义模块 的基本流程

  1. 将相对路径转为绝对路径, 定位目标文件
  2. 缓存检测
  3. 读取目标文件代码
  4. 包裹为一个函数并执行 (自执行函数)。通过 arguments.callee.toString() 查看自执行函数
  5. 缓存模块的值
  6. 返回 module.exports 的值

image-20250610110529684

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
/**
* 伪代码
*/

function require(file){
//1. 将相对路径转为绝对路径,定位目标文件
let absolutePath = path.resolve(__dirname, file);
//2. 缓存检测
if(caches[absolutePath]){
return caches[absolutePath];
}
//3. 读取文件的代码
let code = fs.readFileSync(absolutePath).toString();
//4. 包裹为一个函数 然后执行
let module = {};
let exports = module.exports = {};
(function (exports, require, module, __filename, __dirname) {
const test = {
name: 'yjy'
}

module.exports = test;

//输出
console.log(arguments.callee.toString());
})(exports, require, module, __filename, __dirname)
//5. 缓存结果
caches[absolutePath] = module.exports;
//6. 返回 module.exports 的值
return module.exports;
}

const m = require('./me.js');


五、CommonJS 规范

module.exportsexports 以及 require 这些都是 CommonJS 模块化规范中的内容。

而 Node.js 是实现了 CommonJS 模块化规范,二者关系有点像 JavaScriptECMAScript

CommonJS 规范规定:

  • 每个文件就是一个模块,拥有独立的作用域。

  • 模块内部通过

    1
    module.exports

    1
    exports

    暴露数据。

    • module.exports 可以暴露任意类型的值(对象、函数、基本数据类型等)。
    • exports 实际上是 module.exports 的一个引用(exports = module.exports = {}),所以通过 exports.xxx = value 的方式可以为 module.exports 对象添加属性。但不能直接给 exports 赋值(如 exports = value),否则会切断与 module.exports 的联系。
  • 模块之间通过 require 函数导入其他模块暴露出来的数据。

在 Node.js 中遵循 CommonJS 规范进行模块化开发,使得代码的组织和复用更加方便、规范。例如:

模块 A(moduleA.js

1
2
3
4
5
// 暴露一个函数
function add(a, b) {
return a + b;
}
module.exports = add;

模块 B(moduleB.js

1
2
3
4
// 导入模块 A
const add = require('./moduleA.js');
const result = add(3, 5);
console.log(result); // 输出 8

这样,通过 module.exports 暴露函数,require 导入并使用,实现了模块间的功能复用和代码组织。

CommonJS 规范在服务器端(如 Node.js 环境)应用广泛,它的同步加载机制(在服务器端,文件读取等操作相对快速且对同步性要求较高)是其特点之一。但在浏览器端,由于同步加载会阻塞页面渲染等问题,后来出现了其他模块化规范(如 AMD、CMD、ES6 模块等)来适应浏览器环境的需求。不过在 Node.js 中,CommonJS 规范依然是核心的模块化方式,并且非常适合服务器端的开发场景,如构建 Web 服务器、处理文件 I/O 等任务时的代码模块化组织。

包管理工具

像极了哆啦A梦的口袋

一、概念介绍

1.1 包是什么

『包』英文单词是 package ,代表了一组特定功能的源码集合

1.2 包管理工具

管理『包』的应用软件,可以对「包」进行 下载安装更新删除上传 等操作

借助包管理工具,可以快速开发项目,提升开发效率

包管理工具是一个通用的概念,很多编程语言都有包管理工具,所以 掌握好包管理工具非常重要

1.3 常用的包管理工具

下面列举了前端常用的包管理工具

  • npm
  • yarn
  • cnpm

二、npm

npm 全称 Node Package Manager ,翻译为中文意思是『Node 的包管理工具』

1
npm` 是 `node.js` 官方内置的包管理工具,是 `必须要掌握住的工具

2.1 npm 的安装

node.js 在安装时会 自动安装 npm ,所以如果你已经安装了 node.js,可以直接使用 npm 可以通过 npm -v 查看版本号测试,如果显示版本号说明安装成功,反之安装失败

2.2 npm 基本使用

2.2.1 初始化

创建一个空目录,然后以此目录作为工作目录 启动命令行工具 ,执行 npm init

npm init 命令的作用是将文件夹初始化为一个『包』,

交互式创建 package.json 文件 package.json 是包的配置文件,每个包都必须要有 package.json

package.json 内容示例

1
2
3
4
5
6
7
8
9
10
11
{
"name": "01_npm",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
1
2
3
4
5
6
7
8
9
10
11
{
"name": "1-npm", #包的名字
"version": "1.0.0", #包的版本
"description": "", #包的描述
"main": "index.js", #包的入口文件
"scripts": { #脚本配置
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "", #作者
"license": "ISC" #开源证书
}

初始化的过程中还有一些注意事项:

  1. package name( 包名 )不能使用中文、大写,默认值是 文件夹的名称 ,所以文件夹名称也不能使用中文和大写
  2. version( 版本号 )要求 x.x.x 的形式定义, x 必须是数字,默认值是 1.0.0
  3. ISC 证书与 MIT 证书功能上是相同的,关于开源证书扩展阅读 http://www.ruanyifeng.com/blog/2011/05/how_to_choose_free_software_licenses.html
  4. package.json 可以手动创建与修改
  5. 使用 npm init -y 或者 npm init --yes 极速创建 package.json

2.2.2 搜索包

搜索包的方式有两种:

  1. 命令行:npms/search关键字npms/search关键字
  2. 网站搜索:网址是 https://www.npmjs.com/

经常有同学问,『我怎样才能精准找到我需要的包?』

这个事儿需要大家在实践中不断的积累,通过看文章,看项目去学习去积累。

2.2.3 下载安装包

我们可以通过 npm installnpm i 命令安装包。

1
2
3
4
5
6
7
# 格式
npm install <包名>
npm i <包名>

# 示例 移除数组当中的重复内容
npm install uniq
npm i uniq

运行之后文件夹下会增加两个资源:

  • node_modules 文件夹:存放下载的包
  • package-lock.json:包的锁文件,用来锁定包的版本

安装 uniq 之后, uniq 就是当前这个包的一个依赖包,有时会简称为依赖。

比如我们创建一个包名字为 AA 中安装了包名字是 B,我们就说 BA 的一个依赖包,也会说 A 依赖 B

1
2
3
4
5
6
//1.导入
const uniq = require('uniq');

//2.使用
const arr = [1, 2, 3, 4, 5, 4, 3, 2, 1];
console.log(uniq(arr));
1
2
3
4
5
6
D:\Note\code\nodejs\test>npm i uniq

added 1 package in 4s

D:\Note\code\nodejs\test>node index.js
[ 1, 2, 3, 4, 5 ]

2.2.4 require 导入 npm 包基本流程

  1. 在当前文件夹下 node_modules 中寻找同名的文件夹。
  2. 在上级目录中下的 node_modules 中寻找同名的文件夹,直至找到磁盘根目录。
1
2
3
4
5
6
7
8
//1.导入
// const uniq = require('uniq');//导入的是node_modules目录下的uniq模块 推荐这个 优势:找不到会往上找 直到找到 /
// const uniq = require('./node_modules/uniq'); //导入文件夹 逻辑是去这个文件夹当中找package.json当中main属性所对应的文件
// const uniq = require('./node_modules/uniq/uniq.js');

//2.使用
const arr = [1, 2, 3, 4, 5, 4, 3, 2, 1];
console.log(uniq(arr));

2.3 生产环境与开发环境

开发环境是程序员专门用来写代码的环境,一般是指程序员的电脑,开发环境的项目一般只能程序员自己访问

生产环境是项目代码正式运行的环境,一般是指正式的服务器电脑,生产环境的项目一般每个客户都可以访问

2.4 生产依赖与开发依赖

我们可以在安装时设置选项来区分依赖的类型,目前分为两类:

类型 命令 补充
生产依赖 npm i -S uniq / npm i --save uniq -S 等效于 --save,-S 是默认选项包信息保存在 package.json 中 dependencies 属性
开发依赖 npm i -D less / npm i --save-dev less -D 等效于 --save-dev包信息保存在 package.json 中 devDependencies 属性

举个例子方便大家理解,比如说做蛋炒饭需要大米,油,葱,鸡蛋,锅,煤气,铲子等。

其中锅,煤气,铲子属于开发依赖,只在制作阶段使用。

而大米,油,葱,鸡蛋属于生产依赖,在制作与最终食用都会用到。

所以开发依赖是只在开发阶段使用的依赖包,而生产依赖是开发阶段和最终上线运行阶段都用到的依赖包。

2.5 全局安装

我们可以执行安装选项 -g 进行全局安装

1
npm i -g nodemon 
1
2
3
4
5
6
D:\Note\code\nodejs\test>npm i -g nodemon 

added 29 packages in 4s

4 packages are looking for funding
run `npm fund` for details

全局安装完成之后就可以在命令行的任何位置运行 nodemon 命令。 该命令的作用是 自动重启 node 应用程序。

说明

  • 全局安装的命令不受工作目录位置影响。
  • 可以通过 npm root -g 查看全局安装包的位置。
  • 不是所有的包都适合全局安装,只有全局类的工具才适合,可以通过 查看包的官方文档 来确定安装方式。

2.5.1 修改 Windows 执行策略

image-20250610120349606

windows 默认不允许 npm 全局命令执行脚本文件,所以需要修改执行策略

  1. 以 管理员身份 打开 powershell 命令行

image-20250610120420231

  1. 键入命令 set-ExecutionPolicy remoteSigned

image-20250610120442762

  1. 键入 A 然后敲回车 👌 4. 如果不生效,可以尝试重启 vscode

    npm i -g nodemon

D:\Note\code\nodejs\test>npm install -g nodemon

changed 29 packages in 2s

D:\Note\code\nodejs\test>nodemon index.js
‘nodemon’ 不是内部或外部命令,也不是可运行的程序

解决:

遇到的 “nodemon’ 不是内部或外部命令,也不是可运行的程序或批处理文件” 的问题,通常是由于以下几个原因造成的,即使您已经配置了环境变量:

  1. 环境变量配置未生效或配置错误:

    • 未重启命令行工具或计算机: 修改环境变量后,需要重新打开命令行工具(如 CMD 或 PowerShell)才能使新的环境变量生效。有时甚至需要重启计算机。

    • 路径错误:

      请确保您在系统环境变量

      1
      Path

      中添加的路径是

      1
      npm

      全局安装模块的正确路径。您可以通过以下命令查看

      1
      npm

      的全局安装路径(prefix):

      1
      npm config get prefix

      假设该命令返回的路径是

      1
      C:\Users\YourUsername\AppData\Roaming\npm

      (这只是一个例子,您的路径可能不同),那么您需要确保这个路径(或者这个路径下的

      1
      node_modules\.bin

      目录,具体取决于您的 Node.js 和 npm 版本及配置)被添加到了系统环境变量

      1
      Path

      中。

    • 用户变量与系统变量: 建议将 Node.js 和 npm 相关的路径添加到系统变量的 Path 中,而不是用户变量的 Path 中,以确保所有用户和所有终端都能访问。

    • NODE_PATH 环境变量: 有些教程可能会提到配置 NODE_PATH,但 NODE_PATH 主要用于 require 模块时的查找路径,对于命令行工具的识别,关键在于 Path 环境变量。

  2. npm 全局安装路径问题:

    • 自定义了全局安装路径: 如果您之前通过 npm config set prefix "新的路径" 命令修改了 npm 的全局安装路径,那么您需要将这个“新的路径”添加到系统环境变量 Path 中。 例如,如果您将全局路径设置为 D:\nodejs\node_global,那么 D:\nodejs\node_global 需要在 Path 环境变量中。
    • node_modules\.bin 目录: 全局安装的命令行工具(如 nodemon)的可执行文件通常位于全局安装路径下的 node_modules\.bin 目录。请确保这个 .bin 目录也包含在 Path 环境变量中,或者其父目录已在 Path 中且 npm 能正确处理。
  3. 安装问题:

    • nodemon 未成功全局安装:

      虽然提示

      1
      changed 29 packages

      ,但可能存在某些原因导致

      1
      nodemon

      没有正确安装到预期的全局位置。您可以尝试重新全局安装

      1
      nodemon

      1
      2
      npm uninstall -g nodemon
      npm install -g nodemon
    • 权限问题: 在某些情况下,尤其是在 Windows 上,可能需要以管理员权限运行命令行工具来执行全局安装命令 npm install -g nodemon

  4. PowerShell 执行策略 (如果使用 PowerShell):

    • 如果是在 PowerShell 中运行

      1
      nodemon

      命令,并且遇到类似“无法加载文件…因为在此系统上禁止运行脚本”的错误,这可能是由于 PowerShell 的执行策略限制。 你可以尝试以管理员身份打开 PowerShell,然后运行以下命令来更改执行策略:

      1
      Set-ExecutionPolicy RemoteSigned

      然后按提示输入

      1
      Y

      1
      A

      请注意:

      更改执行策略可能会带来安全风险,请了解其含义后再操作。

排查步骤建议:

  1. 确认 npm 全局路径:
    打开新的命令行窗口,输入 npm config get prefix,记录下输出的路径。

  2. 检查环境变量 Path

    • 打开“我的电脑”或“此电脑” -> 右键“属性” -> “高级系统设置” -> “环境变量”。
    • 在“系统变量”下找到名为 Path 的变量,双击编辑。
    • 确认上一步得到的 npm 全局路径(例如 C:\Users\YourUsername\AppData\Roaming\npm 或者您自定义的全局路径如 D:\nodejs\node_global)是否已经添加到了 Path 变量列表中。 如果没有,请添加它。
  3. 重启命令行/计算机: 修改环境变量后,务必关闭所有已打开的命令行窗口,然后重新打开一个新的,或者直接重启计算机。

  4. 重新尝试运行 nodemon

    1
    2
    nodemon --version
    nodemon index.js
  5. 检查 nodemon 是否在全局路径下:
    导航到 npm config get prefix 返回的路径,查看该路径下是否有 nodemonnodemon.cmd 文件,以及是否存在 node_modules\.bin 文件夹,里面是否有 nodemon 相关文件。

  6. 尝试使用 npx

    1
    npx

    是 npm 5.2+ 自带的工具,可以运行本地或远程 npm 包中的命令,即使没有全局安装。可以临时用它来测试:

    1
    npx nodemon index.js

    如果这个命令能成功运行,说明

    1
    nodemon

    本身是好的,问题更可能出在全局环境配置上。

如果以上步骤都无法解决问题,请提供更多信息,例如:

  • npm config get prefix 的输出结果。
  • 您的环境变量 Path 的具体内容截图或文本。
  • Node.js 和 npm 的版本 (node -vnpm -v)。

2.5.2 环境变量 Path

Path 是操作系统的一个环境变量,可以设置一些文件夹的路径,在当前工作目录下找不到可执行文件 时,就会在环境变量 Path 的目录中挨个的查找,如果找到则执行,如果没有找到就会报错

image-20250610120647937

补充说明

  • 如果希望某个程序在任何工作目录下都能正常运行,就应该将该程序的所在目录配置到环境变量 Path 中。
  • windows 下查找命令的所在位置
    • cmd 命令行 中执行 where nodemon
    • powershell 命令行 执行 get-command nodemon

2.6 安装包依赖

在项目协作中有一个常用的命令就是 npm i,通过该命令可以依据 package.jsonpackage-lock.json 的依赖声明安装项目依赖。

1
2
npm i
npm install

node_modules 文件夹大多数情况都不会存入版本库。

因为node_modules 非常大 我们不用把它上传到git上,只需要有package-lock.json 就可以了

2.7 安装指定版本的包

项目中可能会遇到版本不匹配的情况,有时就需要安装指定版本的包,可以使用下面的命令。

格式

1
npm i <包名@版本号>

示例

1
npm i jquery@1.11.2

2.8 删除依赖

项目中可能需要删除某些不需要的包,可以使用下面的命令:

  • 局部删除
1
2
npm uninstall uniq
npm r uniq
  • 全局删除
1
npm remove -g nodemon

2.9 配置命令别名

通过配置命令别名可以更简单的执行命令。

  • 配置package.json中的 scripts 属性
1
2
3
4
5
6
{
"scripts": {
"server": "node server.js",
"start": "node index.js"
}
}
  • 配置完成之后,可以使用别名执行命令
1
2
npm run server
npm run start

注:start 别名比较特别,使用时可以省略 run

1
npm start

补充说明:

  • npm start 是项目中常用的一个命令,一般用来启动项目。
  • npm run 有自动向上级目录查找的特性,跟 require 函数也一样。
  • 对于陌生的项目,我们可以通过查看 scripts 属性来参考项目的一些操作。

三、cnpm

3.1 介绍

cnpm 是一个淘宝构建的 npmjs.com 的完整镜像,也称为「淘宝镜像」,网址 https://npmmirror.com/

cnpm 服务器部署在国内阿里云服务器上,可以提高包的下载速度

官方也提供了一个全局工具包 cnpm,操作命令与 npm 大体相同

3.2 安装

我们可以通过 npm 来安装 cnpm 工具

1
npm install -g cnpm --registry=https://registry.npmmirror.com

3.3 操作命令

功能 命令
初始化 cnpm init / cnpm init
安装包 cnpm i uniq cnpm i -S uniq cnpm i -D uniq cnpm i -g nodemon
安装项目依赖 cnpm i
删除 cnpm runiq

3.4 npm 配置淘宝镜像

用 npm 也可以使用淘宝镜像,配置的方式有两种:

  • 直接配置
  • 工具配置

3.4.1 直接配置

执行如下命令即可完成配置

1
npm config set registry https://registry.npmmirror.com/

3.4.2 工具配置(示例使用 nrm 工具)

1
2
1. **安装 nrm**
npm i -g nrm
  1. 修改镜像
1
nrm use taobao 
  1. 检查是否配置成功(选做)
1
npm config list 

检查 registry 地址是否为 https://registry.npmmirror.com/,如果 则表明成功

补充说明:

  1. 建议使用第二种方式(nrm 配置) 进行镜像配置,因为后续修改起来会比较方便
  2. 虽然 cnpm 可以提高速度,但是 npm 也可以通过淘宝镜像进行加速,所以 npm 的使用率还是高于 cnpm

四、yarn

4.1 yarn 介绍

yarn 是由 Facebook 在 2016 年推出的新的 Javascript 包管理工具,官方网址:https://yarnpkg.com/

4.2 yarn 特点

yarn 官方宣称的一些特点

  • 速度超快:yarn 缓存了每个下载过的包,所以再次使用时无需重复下载。同时利用并行下载以最大化资源利用率,因此安装速度更快
  • 超级安全:在执行代码之前,yarn 会通过算法校验每个安装包的完整性
  • 超级可靠:使用详细、简洁的锁文件格式和明确的安装算法,yarn 能够保证在不同系统上无差异的工作

4.3 yarn 安装

我们可以使用 npm 安装 yarn

1
npm i -g yarn

4.4 yarn 常用命令

功能 命令
初始化 yarn init / yarn init -y
安装包 yarn add uniq(生产依赖)yarn add less --dev(开发依赖)yarn global add nodemon(全局安装)
删除包 yarn remove uniq(删除项目依赖包)yarn global remove nodemon(全局删除包)
安装项目依赖 yarn
运行命令别名 yarn <别名>(不需要添加 run

4.5 yarn 配置淘宝镜像

可以通过如下命令配置淘宝镜像

1
yarn config set registry https://registry.npmmirror.com/

可以通过 yarn config list 查看 yarn 的配置项

4.6 npm 和 yarn 选择

大家可以根据不同的场景进行选择:

  1. 个人项目 如果是个人项目,哪个工具都可以,可以根据自己的喜好来选择。
  2. 公司项目 如果是公司项目要根据项目代码来选择,可以通过锁文件判断项目的包管理工具:
    • npm 的锁文件为 package-lock.json
    • yarn 的锁文件为 yarn.lock

包管理工具不要混用,切记,切记,切记

五、管理发布包

5.1 创建与发布

我们可以将自己开发的工具包发布到 npm 服务上,方便自己和其他开发者使用,操作步骤如下:

  1. 创建文件夹,并创建文件 index.js,在文件中声明函数,使用 module.exports 暴露。
  2. npm 初始化工具包,package.json 填写包的信息(包的名字是唯一的)。
  3. 注册账号 https://www.npmjs.com/signup。
  4. 激活账号(一定要激活账号)。
  5. 修改为官方的官方镜像(命令行中运行 nrm use npm)。
  6. 命令行下 npm login 填写相关用户信息。
  7. 命令行下 npm publish 提交包。

5.2 更新包

后续可以对自己发布的包进行更新,操作步骤如下:

  1. 更新包中的代码。
  2. 测试代码是否可用。
  3. 修改 package.json 中的版本号。
  4. 发布更新。npm publish
1
npm publish

六、扩展内容

在很多语言中都有包管理工具,比如:

语言 包管理工具
PHP composer
Python pip
Java maven
Go go mod
JavaScript npm / yarn / cnpm / other
Ruby rubyGems

除了编程语言领域有包管理工具之外,操作系统层面也存在包管理工具,不过这个包指的是【软件包】

操作系统 包管理工具 网址
Centos yum https://packages.debian.org/stable/
Ubuntu apt https://packages.ubuntu.com/
MacOS homebrew https://brew.sh/
Windows chocolatey https://chocolatey.org/

nvm

一、介绍

nvm 全称 Node Version Manager,顾名思义它是用来管理 node 版本的工具,方便切换不同版本的 Node.js

二、使用

nvm 的使用非常的简单,跟 npm 的使用方法类似

2.1 下载安装

首先先下载 nvm,下载地址 https://github.com/coreybutler/nvm-windows/releases ,选择 nvm-setup.exe 下载即可

2.2 常用命令

命令 说明
nvm list available 显示所有可以下载的 Node.js 版本
nvm list 显示已安装的版本
nvm install 18.12.1 安装 18.12.1 版本的 Node.js
nvm install latest 安装最新版的 Node.js
nvm uninstall 18.12.1 删除某个版本的 Node.js
nvm use 18.12.1 切换 18.12.1 的 Node.js

ExpressJS

一、express 介绍

express 是一个基于 Node.js 平台的极简、灵活的 WEB 应用开发框架,官方网址:https://www.expressjs.com.cn/

简单来说,express 是一个封装好的工具包,封装了很多功能,便于我们开发 WEB 应用(HTTP 服务)

二、express 使用

2.1 express 下载

express 本身是一个 npm 包,所以可以通过 npm 安装

1
2
npm init
npm i express

2.2 express 初体验

大家可以按照这个步骤进行操作:

  1. 创建 JS 文件,键入如下代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//1. 导入 express
const express = require('express');

//2. 创建应用对象
const app = express();

//3. 创建路由规则
app.get('/home', (req, res) => {
res.end('hello express server');
});

//4. 监听端口 启动服务
app.listen(3000, () =>{
console.log('服务已经启动, 端口监听为 3000...');
});

命令行下执行:

1
2
3
node <文件名>
# 或者
nodemon <文件名>

访问 http://127.0.0.1:3000/home 可查看结果。

三、express 路由

3.1 什么是路由

官方定义:路由确定了应用程序如何响应客户端对特定端点的请求。

3.2 路由的使用

一个路由的组成有请求方法路径回调函数组成。

express 中提供了一系列方法,可以很方便的使用路由,使用格式如下:

1
app.<method>(path, callback)

代码示例:

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
//导入 express
const express = require('express');

//创建应用对象
const app = express();

//创建 get 路由
app.get('/home', (req, res) => {
res.send('网站首页');
});

//首页路由
app.get('/', (req, res) => {
res.send('我才是真正的首页');
});

//创建 post 路由
app.post('/login', (req, res) => {
res.send('登录成功');
});

//匹配所有的请求方法
app.all('/search', (req, res) => {
res.send('1 秒钟为您找到相关结果约 100,000,000 个');
});

//自定义 404 路由
app.all('*', (req, res) => {
res.send('<h1>404 Not Found</h1>')
});

//监听端口 启动服务
app.listen(3000, () =>{
console.log('服务已经启动, 端口监听为 3000');
});

3.3 获取请求参数

express 框架封装了一些 API 来方便获取请求报文中的数据,并且兼容原生 HTTP 模块的获取方式

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
//导入 express
const express = require('express');

//创建应用对象
const app = express();

//获取请求的路由规则
app.get('/request', (req, res) => {
//1. 获取报文的方式与原生 HTTP 获取方式是兼容的
console.log(req.method);
console.log(req.url);
console.log(req.httpVersion);
console.log(req.headers);

//2. express 独有的获取报文的方式
//获取查询字符串
console.log(req.query); // [相对重要]
// 获取指定的请求头
console.log(req.get('host'));

res.send('请求报文的获取');
});

//启动服务
app.listen(3000, () => {
console.log('启动成功....')
})

3.4 获取路由参数

路由参数指的是 URL 路径中的参数(数据)

1
2
3
app.get('/:id.html', (req, res) => {
res.send('商品详情,商品 id 为' + req.params.id);
});

练习


根据路由参数响应歌手的信息

路径结构如下

1
/singer/1.html

显示歌手的姓名图片

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
{
"singers": [
{
"singer_name": "周杰伦",
"singer_pic": "http://y.gtimg.cn/music/photo_new/T001R150x150M0000025NhlN2yWrP4.webp",
"other_name": "Jay Chou",
"singer_id": 4558,
"id": 1
},
{
"singer_name": "林俊杰",
"singer_pic": "http://y.gtimg.cn/music/photo_new/T001R150x150M000001BLpXF2DyJe2.webp",
"other_name": "JJ Lin",
"singer_id": 4286,
"id": 2
},
{
"singer_name": "G.E.M. 邓紫棋",
"singer_pic": "http://y.gtimg.cn/music/photo_new/T001R150x150M000001fNHEf1SFEFN.webp",
"other_name": "Gloria Tang",
"singer_id": 13948,
"id": 3
},
{
"singer_name": "薛之谦",
"singer_pic": "http://y.gtimg.cn/music/photo_new/T001R150x150M000002J4UUk29y8BY.webp",
"other_name": "",
"singer_id": 5062,
"id": 4
}
]
}

四、express 响应设置

express 框架封装了一些 API 来方便给客户端响应数据,并且兼容原生 HTTP 模块的获取方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//获取请求的路由规则
app.get("/response", (req, res) => {
//1. express 中设置响应的方式兼容 HTTP 模块的方式
res.statusCode = 404;
res.statusMessage = 'xxx';
res.setHeader('abc', 'xyz');
res.write('响应体');
res.end('xxx');

//2. express 的响应方法
res.status(500); //设置响应状态码
res.set('xxx', 'yyy');//设置响应头
res.send('中文响应不乱码');//设置响应体 send 不会乱码 会内置一个请求头
//连贯操作
res.status(404).set('xxx', 'yyy').send('你好朋友')

//3. 其他响应
res.redirect('http://baidu.com');//重定向
res.download('/package.json');//下载响应
res.json({});//响应 JSON
res.sendFile(__dirname + '/home.html') //响应文件内容
});

五、express中间件

5.1 什么是中间件

中间件(Middleware)本质是一个回调函数

中间件函数 可以像路由回调一样访问 请求对象(request),响应对象(response)

5.2 中间件的作用

中间件的作用 就是 使用函数封装公共操作,简化代码

5.3 中间件的类型

  • 全局中间件
  • 路由中间件

好比是我们去做高铁,我们都是先把行李过一次安检,我们就叫它安检门吧,这个安检门就是属于全局中间件,然后进入这个安检门以后会有很多通道我们需要刷卡进入 这就是路由中间件。

我们必须先检表再上高铁,不然就会产生很多混乱

5.3.1 定义全局中间件

每一个请求 到达服务端之后 都会执行全局中间件函数

声明中间件函数

1
2
3
4
5
6
let recordMiddleware = function(request, response, next){
//实现功能代码
//.....
//执行next函数(当如果希望执行完中间件函数之后,仍然继续执行路由中的回调函数,必须调用next)
next();
}

image-20250610174956850

应用中间件

1
app.use(recordMiddleware);

声明时可以直接将匿名函数传递给 use

1
2
3
4
app.use(function (request, response, next) {
console.log('定义第一个中间件');
next();
});

5.3.2 多个全局中间件

express 允许使用 app.use() 定义多个全局中间件

1
2
3
4
5
6
7
8
9
app.use(function (request, response, next) {
console.log('定义第一个中间件');
next();
});

app.use(function (request, response, next) {
console.log('定义第二个中间件');
next();
});

5.3.3 定义路由中间件

如果只需要对某一些路由进行功能封装,则就需要路由中间件

调用格式如下:

1
2
3
4
5
6
7
app.get('/路径', '中间件函数', (request, response)=>{

});

app.get('/路径', '中间件函数1', '中间件函数2', (request, response)=>{

});

5.4 静态资源中间件

express 内置处理静态资源的中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//引入express框架
const express = require('express');
//创建服务对象
const app = express();
//静态资源中间件的设置,将当前文件夹下的public目录作为网站的根目录
app.use(express.static('./public'));
//如果访问的内容经常变化,还是需要设置路由

//但是,在这里有一个问题,如果public目录下有index.html文件,单独也有index.html的路由,
//则谁书写在前,优先执行谁
app.get('/index.html',(request,response)=>{
response.send('首页');
});

//监听端口
app.listen(3000,()=>{
console.log('3000端口启动....');
});

注意事项:

  1. index.html 文件为默认打开的资源
  2. 如果静态资源与路由规则同时匹配,谁先匹配谁就响应
  3. 路由响应动态资源,静态资源中间件响应静态资源

5.5 获取请求体数据 body - parser

express 可以使用 body - parser 包处理请求体

第一步:安装

1
npm i body - parser

第二步:导入 body - parser 包

1
const bodyParser = require('body - parser');

第三步:获取中间件函数

1
2
3
4
// 处理 querystring 格式的请求体
let urlParser = bodyParser.urlencoded({ extended: false });
// 处理 JSON 格式的请求体
let jsonParser = bodyParser.json();

第四步:设置路由中间件,然后使用 request.body 来获取请求体数据

1
2
3
4
5
6
7
8
9
app.post('/login', urlParser, (request, response) => {
// 获取请求体数据
// console.log(request.body);
// 用户名
console.log(request.body.username);
// 密码
console.log(request.body.userpass);
response.send('获取请求体数据');
});

获取到的请求体数据:

1
[Object: null prototype] { username: 'admin', userpass: '123456' }

六、Router

6.1 什么是 Router

express 中的 Router 是一个完整的中间件和路由系统,可以看做是一个小型的 app 对象。

6.2 Router 作用

对路由进行模块化,更好的管理路由。

6.3 Router 使用

  • 创建独立的 JS 文件(homeRouter.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
t//1. 导入 express
const express = require('express');

//2. 创建路由器对象
const router = express.Router();

//3. 在 router 对象身上添加路由
router.get('/', (req, res) => {
res.send('首页');
});

router.get('/cart', (req, res) => {
res.send('购物车');
});

//4. 暴露
module.exports = router;
  • 主文件
1
2
3
4
5
6
7
8
9
const express = require('express');
const app = express();
//5.引入子路由文件
const homeRouter = require('./routes/homeRouter');
//6.设置和使用中间件
app.use(homeRouter);
app.listen(3000,()=>{
console.log('3000 端口启动....');
})

七、EJS 模板引擎

7.1 什么是模板引擎

模板引擎是分离用户界面和业务数据的一种技术。

7.2 什么是 EJS

EJS 是一个高效的 Javascript 的模板引擎。

7.3 EJS 初体验

下载安装 EJS

1
npm i ejs --save

代码示例

1
2
3
4
5
6
7
8
9
// 1.引入ejs
const ejs = require('ejs');
// 2.定义数据
let person = ['张三','李四','王二麻子'];
// 3.ejs解析模板返回结构
// <%%> 是ejs解析内容的标记,作用是输出当前表达式的执行结构
let html = ejs.render('<%= person.join(", ") %>', {person:person});
// 4.输出结果
console.log(html);

命令行下运行

7.4 EJS常用语法

执行JS代码

1
<% code %>

输出转义的数据到模板上

1
<%= code %>

输出非转义的数据到模板上

1
2

<%- code %>

MongoDB

一、简介

1.1 MongoDB是什么

MongoDB是一个基于分布式文件存储的数据库,官方地址 https://www.mongodb.com/

1.2 数据库是什么

数据库(DataBase)是按照数据结构来组织、存储和管理数据的<应用程序>

1.3 数据库的作用

数据库的主要作用就是<管理数据>,对数据进行<增(c)、删(d)、改(u)、查(r)>

1.4 数据库管理数据的特点

相比于纯文件管理数据,数据库管理数据有如下特点:

  1. 速度更快
  2. 扩展性更强
  3. 安全性更强

1.5 为什么选择 MongoDB

操作语法与 JavaScript 类似,容易上手,学习成本低

二、核心概念

Mongodb中有三个重要概念需要掌握

  • 数据库(database):数据库是一个数据仓库,数据库服务下可以创建很多数据库,数据库中可以存放很多集合
  • 集合(collection):集合类似于JS中的数组,在集合中可以存放很多文档
  • 文档(document):文档是数据库中的最小单位,类似于JS中的对象

大家可以通过 JSON 文件来理解 Mongodb 中的概念

  • 一个 JSON 文件 好比是一个 数据库,一个 Mongodb 服务下可以有 N 个数据库
  • JSON 文件中的一级属性的数组值 好比是 集合
  • 数组中的对象好比是 文档
  • 对象中的属性有时也称之为 字段

一般情况下

  • 一个项目使用一个数据库
  • 一个集合会存储同一种类型的数据

三、下载安装与启动

下载地址: https://www.mongodb.com/try/download/community 建议选择 zip 类型,通用性更强

配置步骤如下:

1> 将压缩包移动到 C:\Program Files 下,然后解压

2> 创建 C:\data\db 目录,mongodb 会将数据默认保存在这个文件夹

3> 以 mongodb 中 bin 目录作为工作目录,启动命令行

4> 运行命令 mongod

会话控制

一、介绍

所谓会话控制就是 对会话进行控制

HTTP 是一种无状态的协议,它没有办法区分多次的请求是否来自于同一个客户端, 无法区分用户

而产品中又大量存在的这样的需求, 所以我们需要通过 会话控制 来解决该问题

常见的会话控制技术有三种:

  • cookie
  • session
  • token

二、cookie

cookie 是 HTTP 服务器发送到用户浏览器并保存在本地的一小块数据

cookie 是保存在浏览器端的一小块数据

cookie 是按照域名划分保存的

简单示例:

域名 cookie
https://www.baidu.com a=100; b=200
https://www.bilibili.com xid=1020abce121; hm=112411213
jd.com x=100; ocw=12414cce

浏览器向服务器发送请求时,会自动将 当前域名下 可用的 cookie 设置在请求头中,然后传递给服务器

这个请求头的名字也叫 cookie ,所以将 cookie 理解为一个 HTTP 的请求头也是可以的 2.3 cookie 的运行流程 填写账号和密码校验身份,校验通过后下发 cookie

image-20250610180549565

有了 cookie 之后,后续向服务器发送请求时,就会自动携带 cookie

image-20250610180611114

三、Session

3.1 Session 是什么

Session 是保存在服务器端的一块儿数据,保存当前访问用户的相关信息。

3.2 Session 的作用

实现会话控制,可以识别用户的身份,快速获取当前用户的相关信息。

3.3 Session 运行流程

  1. 用户填写账号和密码校验身份,校验通过后创建 session 信息。
  2. 服务器将 session_id 的值通过响应头返回给浏览器。
  3. 当浏览器再次发送请求时,会将 session_id 携带在请求头中。
  4. 服务器接收到请求后,根据 session_id 找到对应的 session 信息,从而知道当前用户的身份等相关信息。

image-20250610180718738

有了 cookie,下次发送请求时会自动携带 cookie,服务器通过 cookie 中的 session_id 的值确定用 户的身份

image-20250610180739400

类似比喻:好比去理发店,一般会有会员说是如果不充值35 充值25且这一次免费,充值玩后它会给你一张卡,然后他会再它的笔记本上记录信息 以后要剪头发只要去笔记本里看看有没有你的信息即可。

四、session 和 cookie 的区别

cookie 和 session 的区别主要有如下几点:

1. 存在的位置

  • cookie:浏览器端
  • session:服务端

2. 安全性

  • cookie 是以明文的方式存放在客户端的,安全性相对较低
  • session 存放于服务器中,所以安全性相对较好

3. 网络传输量

  • cookie 设置内容过多会增大报文体积,会影响传输效率
  • session 数据存储在服务器,只是通过 cookie 传递 id,所以不影响传输效率

4. 存储限制

  • 浏览器限制单个 cookie 保存的数据不能超过 4K,且单个域名下的存储数量也有限制
  • session 数据存储在服务器中,所以没有这些限制