前言:参考 Node.js 中文网,可以通过 Node.Js V19 API 文档查看常用 API 。
番外
番外知识点,多学点总没坏处 😀😀😀。
1. 计算机的基本组成
计算机由 CUP、内存、硬盘、显卡、主板、散热器 等组成。
1.1 CUP
中央处理器,是整个计算机运算和控制的中心
1.2 内存
是存储数据的介质,读写速度快,但是断电丢失数据。我们程序在运行时,就会载入内存当中,让 CUP 高速运行程序。
1.3 硬盘
和内存很像,用于存放数据,读写速度慢,断电不丢失数据。我们安装的应用程序就装在硬盘中,比如 QQ、微信等
1.4 显卡
处理视频信号的,当有信息需要在显示器呈现时,就要传递给显卡,显卡处理完毕后,再传输给显示器。
1.5 主板
集成电路板,上面有很多插槽,可以插入 CUP 处理器,内存条、硬盘、显卡等。
2. 程序运行基本流程
安装的程序在硬盘,运行程序在内存,CUP 从内存中拿程序执行,执行时有视频信号传给显卡,有声音信号传给声卡。
3. 进程和线程
3.1 进程
运行中的程序,每一个运行中的程序都有一个属于自己的进程,这些进程占用内存空间。
3.2 线程
进程中的任务,一个进程至少有一个任务,就是一个线程,不然你开了一个程序,但是这个程序什么都不做,那它就没有存在的意义。也可以有多个任务,就是我们常说的多线程。
4. HTTP 协议
超文本传输协议,是互联网运用最广泛的协议之一。协议是什么?协议就是双方必须遵从的一组约定。HTTP 协议是对浏览器和服务器双方的约定。
4.1 请求报文
请求报文由请求行、请求头、请求体构成。以访问百度为例。
4.1.1 请求行
由三部分组成:请求方法、URL、HTTP 协议版本号。
请求方法:GET 获取数据、POST 新增数据、PUT/PATCH 修改数据、DELETE 删除数据
URL:用来定位服务器资源,由协议、域名、端口、路径、查询字符串组成
HTTP 版本号:有 1.0、1.1、2、3 四个版本号
GET https://www.baidu/com/ HTTP/1.1
4.1.2 请求头
由一系列的键值对组成,用来记录浏览器的相对信息和交互行为等。
HTTP/1.1 200 OK
Bdpagetype: 1
Bdqid: 0x9a760cca0000e1ee
Connection: keep-alive
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
Date: Fri, 21 Jun 2024 03:44:22 GMT
Server: BWS/1.1
Set-Cookie: H_PS_PSSID=60297_60338_60352_60346_60364_60360; path=/; expires=Sat, 21-Jun-25 03:44:22 GMT; domain=.baidu.com
Set-Cookie: BDSVRTM=8; path=/
Set-Cookie: BD_HOME=1; path=/
Strict-Transport-Security: max-age=172800
Traceid: 1718941462054085735411130097590815744494
X-Ua-Compatible: IE=Edge,chrome=1
X-Xss-Protection: 1;mode=block
Transfer-Encoding: chunked
4.1.3 请求体
请求体的构成就很灵活,我们用的最多的 JSON 格式。
4.2 响应报文
响应报文由响应行、响应头、响应体构成。以访问百度为例。
4.2.1 响应行
由三部分组成:HTTP 版本号、响应状态码、响应状态描述。
HTTP 版本号:有 1.0、1.1、2、3 四个版本号
响应状态码:200 请求成功、403 禁止请求、404 找不到资源、500 服务端错误。
响应状态码五大类:1xx 信息响应、2xx 成功响应、3xx 重定向信息、4xx 客户端错误、5xx 服务端错误
响应状态描述:200 OK、403 Forbidden、404 Not Found、500 Internal Server Error
HTTP/1.1 200 OK
4.2.2 响应头
记录服务器相关的内容。
4.2.3 响应体
响应体内容格式很灵活:HTML、CSS、JS、图片、视频、JSON
5. 网络基础概念
5.1 什么是 IP
就是你上网设备在互联网上的地址。比如:192.168.1.1
5.2 IP 有什么用
IP 用来标识网络中的设备,实现设备通信。只要设备接入互联网,就都会有一个 IP 地址。当你给你小伙伴发消息时,你发的消息就带有你的 IP 地址和你小伙伴的 IP 地址,这样才能实现通信。
5.3 IP 分类
每一个接入互联网的设备都有一个自己的 IP 地址,而互联网上的 IP 总共也才 42 亿,全球人口 80 亿+,每个人的联网设备还不止一个。有手机,电脑,手表,电视,智能家居等,这么多设备根本不够分。所以就有了共享 IP,分为区域共享 IP 或者家庭共享 IP。比如你家里有手机、笔记本、电视,然后这些设备通过 wifi 或者网线都链接到了路由器上,路由器会为每个设备分配 IP 地址,路由器本身也有 IP 地址比如 192.168.1.1,给你手机分个 IP 192.168.1.2,电脑 192.168.1.3,电视 192.168.1.4。我们的设备通过路由器链接到了一起,形成了个网络,这我们称之为局域网。路由器分配给我们的 IP 地址,称为局域网 IP。在这个网络里,我们的设备是可以通信的。但是如果你想给你远方的女友发个消息 “吃了吗”,目前的网络是不行的。需要将路由器接入互联网。如何接入互联网呢,去找电信、联通、移动办理业务。办理完业务后路由器就有了另外一个 IP,比如 180.91.213.151,这就是公网 IP。家里设备共享的 IP 就是这个公网 IP,有了这个 IP,你就可以和远方的女友通信了。
局域网 IP:192.168.0.0 ~ 192.168.255.255、172.16.0.0 ~ 172.31.255.255、10.0.0.0 ~ 10.255.255.255
公网 IP:除了局域网 IP 和本地回环 IP
本地回环 IP:127.0.0.1 ~ 127.255.255.254 这类 IP 地址是指向本机的。称为本地回环地址。
5.4 什么是端口
应用程序的数字标识,一台计算机有 65536 个端口(0 ~ 65535),一个应用程序可以使用一个或多个端口。什么是应用程序?就是你安装到计算机上的软件,比如微信、QQ、游戏等。
5.5 端口有什么用
实现不同主机应用程序之间的通信的。比如两台计算机的微信相互通信,如果只有 IP 没有端口,那计算机不知道接受到的报文,用什么程序去处理。
一:入门指南
1. Node.js 简介
1.1 什么是 Node.js
Node.js 是一个运行环境,这个运行环境是基于谷歌 v8 引擎的,用于在服务端运行 JS 代码。
Node.js 采用事件驱动、非阻塞式 I/O 的设计理念,使 JS 能高效地处理大量并发请求。
1.2 Node.js 可以做什么
开发服务器应用、工具类应用、桌面端应用。
1.2.1 服务器应用
可以对用户请求做出处理,返回资源。
1.2.2 工具类应用
Webpack、Vite、Babel
1.2.3 桌面端应用
VsCode、PostMan、Figma 都是使用 electron 开发的,electron 是基于 node 开发的
1.3 Node.js 的优、缺点
优点:因为 Node.js 采用事件驱动、非阻塞式 I/O 的设计理念,所以使 JS 能高效地处理大量并发请求。
缺点:大前端,木有缺点!
1.4 示例
使用 Node.js 启动一个 Web 服务器,首先创建一个 server.js,并在 server.js 所在文件夹点右键打开终端,运行 node server.js 命令。
// server.js
const http = require("http");const hostname = "127.0.0.1";
const port = 3000;const server = http.createServer((req, res) => {res.statusCode = 200;res.setHeader("Content-Type", "text/plain");res.end("Hello World\n");
});server.listen(port, hostname, () => {console.log(`Server running at http://${hostname}:${port}/`);
});
2. 如何安装 Node.js
点击安装 nodejs,推荐使用 nvm,因为 nvm 是 nodejs 的版本管理工具,便于切换 node 版本,教程 nvm 用法
3. Node.js 和浏览器的区别
可操作的 API 不同:浏览器可以操作 DOM、BOM。Node.js 不行,因为不存在这些,Node.js 有 fs、path、http 等模块可以操作,浏览器没有。
模块系统不同:浏览器只支持 ES 模块系统,Node.js 同时支持 CommonJs 和 ES 模块系统
二、npm 包管理
可以参考 npm 包管理
1. npm 简介
npm 是 Node.js 的标准包管理器。我们习惯使用 yarn 或 pnpm 来代替 npm。因为可以更快安装依赖。
2. 包
就是通过 npm 下载到项目中的依赖统称为包,npm 管理项目依赖的下载。
3. 安装依赖
如果项目有一个 package.json 文件,通过运行 npm install 来安装依赖。它会将所需依赖安装到 node_modules 文件夹,如果尚不存在,则创建它。
npm install
# or
yarn install
# or
pnpm install
4. 安装单个包
可以通过包名来安装特定的包。
npm install <package-name>
// or
yarn add <package-name>
# or
pnpm add <package-name>
5. 更新软件包
npm 将检查所有软件包是否有满足版本控制约束的较新版本。
npm update
# or
yarn update
# or
pnpm update
6. 更新单个包
npm update <package-name>
# or
yarn update <package-name>
# or
pnpm update <package-name>
7. 指定安装固定版本包
指定版本有助于让每个人都使用相同的软件包版本,以便整个团队运行相同的版本。
npm install <package-name>@<version>
# or
yarn add <package-name>@<version>
# or
pnpm add <package-name>@<version>
8. 运行任务
package.json 文件支持一种格式,用于指定可以使用以下方式运行的命令行任务
{"scripts": {"watch": "webpack --watch --progress --colors --config webpack.conf.js","dev": "webpack --progress --colors --config webpack.conf.js","prod": "NODE_ENV=production webpack -p --config webpack.conf.js"}
}
可以通过 npm run <task-name> 来代替那么长串命令。
npm run dev
# or
yarn dev
# or
pnpm dev
9. 安装全局依赖
通过 npm root -g 查看全局安装的位置。nodemon 的作用是动态更新服务文件。
npm install -g nodemon
三、NodeJs 常用模块
1. Buffer 模块
Buffer 是一个固定长度的内存空间,用来处理二进制数据。
Buffer 的特点:1. 大小固定无法调整。2. 性能较好,可以直接对计算机内存进行操作。3. 每个元素的大小为 1 字节。
1.1 Buffer 的创建
Buffer 为全局属性可以直接使用,alloc 代表分配,如下给 buf 变量分配 10 个字节。
let buf = Buffer.alloc(10); // <Buffer 00 00 00 00 00 00 00 00 00 00>
allocUnsafe 代表不安全分配,和上面运行结果一样,有什么区别勒?使用 allocUnsafe 创建的数据可能会包含旧的内存数据。
let buf = Buffer.allocUnsafe(10); // <Buffer 00 00 00 00 00 00 00 00 00 00>
from 可以将字符串、数组转换为 buffer,每个字符都会转换为 unicode 码表码表的数字,数字转换成二进制保存在码表中。
let buf = Buffer.from("yqcoder"); // <Buffer 79 71 63 6f 64 65 72>
1.2 Buffer 与字符串转换
使用 toString() 方法,将 buffer 转换为字符串。
let buf = Buffer.from("yqcoder");
let name = buf.toString(); // yqcoder
1.3 Buffer 和中文转换
中文转 Buffer,一个汉字占 3 个字节
let buf = Buffer.from("你好"); // <Buffer e4 bd a0 e5 a5 bd>
1.4 通过 [] 操作单个字符
和数组类似,通过 [] 下标的方式,实现读取和修改
let buf = Buffer.from("yqcoder");
buf[0]; // 121
2. fs 模块
fs 可以和我们硬盘进行交互,比如文件的创建、删除、重命名、移动。文件内容的写入、读取。和文件夹相关操作。
注:fs 中的相对路径相对的是打开命令行工具的目录,而不是执行文件的目录,这就容易出问题。使用__dirname 拼接绝对路径解决这个问题。__dirname 表示执行文件所在文件夹的绝对路径。__filename 表示执行文件的所在路径
2.1 写入文件
使用 fs.writeFile(file, data[, options], callback) 创建和写入文件,file 文件名、data 写入数据、options 可选配置、callback 回调函数。
writeFile 是一个异步操作,js 的主线程开始执行这段代码,当执行到 writeFile 时会进行磁盘的写入,并将磁盘写入操作交给 I/O 线程去完成。I/O 线程会在写入完毕后,将回调函数放入任务队列中,在主线程代码执行完毕后,再执行任务队列的函数。这就是传说中的事件循环机制。
const fs = require("fs");
fs.writeFile("./demo.txt", "hello yqcoder", (err) => {if (err) {console.log("写入失败");return;}console.log("写入成功!");
});
writeFileSync 同步操作。使用和 writeFile 差不多,只是没有回调函数了,主线程执行到 writeFileSync 时,后停止执行后续代码,等 I/O 线程执行完毕后,再继续执行后续代码。如下使用 try catch 是为了防止读写失败后,阻塞程序继续运行。
const fs = require("fs");
try {fs.writeFileSync("./demo.txt", "yqcoder");
} catch (err) {}
2.2 追加内容
使用 fs.appendFile(file, data[, options], callback),参数含义同 writeFile 的参数。在文件末尾添加内容,如果文件不存在,先创建文件再添加内容。使用 \r\n 实现添加文本换行。同步操作 fs.appendFileSync。
const fs = require("fs");
fs.appendFile("./demo.txt", "\r\nyqcoder", (err) => {if (err) {console.log("写入失败");return;}console.log("写入成功!");
});
也可以使用 fs.writeFile,添加配置。
const fs = require("fs");
fs.writeFile("./demo.txt", "hello yqcoder", { flag: "a" }, (err) => {if (err) {console.log("写入失败");return;}console.log("写入成功!");
});
2.3 流式写入
通过 fs.createWriteStream(path[, options]) 创建一个实例,path 文件路径。options 可选配置。使用实例方法 ws.write(data) 往目标文件写入内容。和 writeFile 的区别是 writeFile 是一次性写入,createWriteStream 是打开一个通道,在通道没有关闭之前,可以断断续续的给文件写入内容。适合写入频率高的场景。
const fs = require("fs");
const ws = fs.createWriteStream("./demo.txt");
ws.write("yqcoder,");
ws.write("hello");
ws.close();
2.4 文件读取
使用 fs.readFile(path[, options], callback) 读取文件,path 文件路径,options 可选配置,callback 回调函数。readFileSync(path[, options])同步读取。
const fs = require("fs");
fs.readFile("./demo.txt", (err, data) => {if (err) {console.log("读取失败");return;}console.log(data); // <Buffer 79 71 63 6f 64 65 72 2c 68 65 6c 6c 6f>console.log(data.toString()); // yqcoder,hello
});
2.5 流式读取
通过 fs.createReadStream(path[, options]) 创建一个实例,path 文件路径。options 可选配置。监听 data 事件 ,ws.on('data', (chunk) => {}) 每次获取 64kb 的文件内容 chunk。读取完成后,触发 end 事件。和 readFile 的区别是 readFile 是一次性读取,createReadStream 是流失读取,能提升读取大文件效率。
const fs = require("fs");
const rs = fs.createReadStream("./demo.txt");
rs.on("data", (chunk) => {console.log(chunk.length);
});
rs.on("end", () => {console.log("读取完成");
});
2.6 重命名和移动
使用 fs.rename(oldPath, newPath, callback) 重命名文件,oldpath 就文件地址,newPath 新文件地址,callback 回调函数。renameSync(oldPath, newPath)同步命名。
使用重命名 API 可以实现文件移动的效果。
const fs = require("fs");
fs.rename("./demo.jpg", "./newDemo.jpg", (err) => {if (err) {console.log("重命名失败");return;}console.log("重命名成功");
});
2.7 文件删除
使用 fs.unlink(path, callbck) 删除文件,path 文件路径,callback 回调函数。unlinkSync(path) 同步删除。
也可以使用 fs.rm(path, callbck) 删除文件,path 文件路径,callback 回调函数。rmSync(path) 同步删除。
const fs = require("fs");fs.unlink("./newDemo.jpg", (err) => {if (err) {console.log("删除失败");return;}console.log("删除成功");
});
// or
fs.rm("./newDemo.jpg", (err) => {if (err) {console.log("删除失败");return;}console.log("删除成功");
});
2.8 文件夹操作
2.8.1 创建
使用 fs.mkdir(path[, options], callback) 创建文件夹,path 文件夹路径,options 可选配置,callback 回调函数。mkdirSync(path[, options]) 同步创建。
const fs = require("fs");
fs.mkdir("./demo", (err) => {if (err) {console.log("创建文件夹失败");return;}console.log("创建文件夹成功");
});
递归创建文件夹,需要添加配置 { recursive: true }
const fs = require("fs");
fs.mkdir("./demo/assets/img", { recursive: true }, (err) => {if (err) {console.log("创建文件夹失败");return;}console.log("创建文件夹成功");
});
2.8.2 读取
使用 readdir(path[, options], callback) 读取文件夹有哪些文件,path 文件夹路径,options 可选配置,callback 回调函数。readdirSync(path[, options]) 同步读取。
const fs = require("fs");
fs.readdir("./demo", (err, data) => {if (err) {console.log("读取文件夹失败");return;}console.log("读取文件夹成功", data); // 读取文件夹成功 [ 'assets' ]
});
读取文件夹下所有文件,需要添加配置 { recursive: true }
const fs = require("fs");
fs.readdir("./demo", { recursive: true }, (err, data) => {if (err) {console.log("读取文件夹失败");return;}console.log("读取文件夹成功", data); // 我没有读取成功,可能 node 版本需要22以上
});
2.8.3 删除
使用 fs.rmdir(path[, options], callbck) 删除文件,path 文件路径,options 可选配置,callback 回调函数。rmdirSync(path[, options]) 同步删除。
注意:删除的文件夹下不能有文件。
const fs = require("fs");fs.rmdir("./demo", (err) => {if (err) {console.log("删除失败");return;}console.log("删除成功");
});
递归删除,删除文件夹包括文件夹所有文件。需要配置 { recursive: true }
const fs = require("fs");fs.rmdir("./demo", { recursive: true }, (err) => {if (err) {console.log("删除失败");return;}console.log("删除成功");
});
2.9 查看资源状态
使用 fs.stat(path[, options], callbck) 查看资源详细信息,path 文件路径,options 可选配置,callback 回调函数。查看成功后回调函数返回的 data 有两个方法,可以判断查看文件的类型,data.isFile(),data.isDirectory()。 rmdirSync(path[, options]) 同步查看。
const fs = require("fs");fs.stat("./demo.mp4", (err, data) => {if (err) {console.log("查看失败");return;}console.log("查看成功", data);// 查看成功 Stats {// dev: 3603322110,// mode: 33206,// nlink: 1,// uid: 0,// gid: 0,// rdev: 0,// blksize: 4096,// ino: 844424930382849,// size: 101030174,// blocks: 197328,// atimeMs: 1718876089782.0842,// mtimeMs: 1718876089782.0842,// ctimeMs: 1718876089782.0842,// birthtimeMs: 1718876088481.4133,// atime: 2024-06-20T09:34:49.782Z,// mtime: 2024-06-20T09:34:49.782Z,// ctime: 2024-06-20T09:34:49.782Z,// birthtime: 2024-06-20T09:34:48.481Z// }console.log(data.isFile()); // trueconsole.log(data.isDirectory()); // false
});
3. path 模块
path 模块是用来操作路径的。
3.1 拼接绝对路径
使用 path.resolve(path[, path]...) 拼接路径,path 文件路径,使用 \_\_dirname 拿到当前文件所在的目录,然后拼接第二个参数,第二个参数需要是相对路径,输出结果为\拼接的路径。
const path = require("path");
path.resolve(__dirname, "./demo.mp4");
// 等于
path.resolve(__dirname, "demo.mp4");
3.2 获取操作系统分隔符
使用 path.sep 获取操作符,不同操作系统分隔符不一样,windows \,Linux /
const path = require("path");
path.sep; // \
3.3 解析路径
使用 path.parse(path) 解析路径,path 文件路径。可以解析文件所在盘符 root、所在文件夹 dir、文件全名 base、拓展符 ext、文件名 name
const path = require("path");
path.parse(__filename);
// {
// root: 'D:\\',
// dir: 'D:\\xxx\\xxx\\xxx\\xxx\\dist',
// base: 'index.js',
// ext: '.js',
// name: 'index'
// }
3.4 获取路径基础名称
使用 path.basename(path) 获取文件名,path 文件路径。
const path = require("path");
console.log(__filename); // D:\xxx\xxx\xxx\xxx\dist\index.js
console.log(path.basename(__filename)); // index.js
3.5 获取路径目录名
使用 path.dirname(path) 获取文件名,path 文件路径。
const path = require("path");
console.log(__filename); // D:\git项目\notes\docs\每日博客\dist\index.js
console.log(path.dirname(__filename)); // D:\git项目\notes\docs\每日博客\dist
3.6 获取路径拓展名
使用 path.extname(path) 获取文件名,path 文件路径。
const path = require("path");
console.log(__filename); // D:\git项目\notes\docs\每日博客\dist\index.js
console.log(path.extname(__filename)); // .js
4. http 模块
4.1 创建服务对象
使用 http.createServer(callbck) 创建服务对象 server,callback 回调函数。
callback(res, req) 接受两个参数,res 请求报文、req 响应报文。
res.end(content):设置响应内容。
res.setHeader(key, value):设置响应头
使用 server.listen(port, callback) 监听端口,启动服务。当服务启动成功执行 listen 的回调。启动成功后 8080 端口就被我们的服务给占了,以后有程序访问我们电脑 8080 端口,我们创建服务的回调函数就会执行。
当我们的服务接受到 http 请求时,执行回调函数。浏览器可以向我们的服务发送 http 请求。
HTTP 协议默认端口 80,HTTPS 协议默认端口 443。
const http = require("http");
const server = http.createServer((req, res) => {res.end("hello man");
});
server.listen(80, () => {console.log("服务启动成功:", "http://127.0.0.1:80");
});
4.2 获取请求报文
获取请求报文数据。
req.method:请求方法
req.url:请求路径,只包含路径和查询条件
req.headers:请求头
req.httpVersion:请求版本
req.on('data', (chunk) => {}):流式获取请求体
req.on('end', () => {}):请求体获取完成
为什么需要获取到请求报文勒?因为我们需要正确返回请求的数据。
const http = require("http");const server = http.createServer((req, res) => {console.log(req);console.log(req.method); // GETconsole.log(req.httpVersion); // 1.1console.log(req.url); // /console.log(req.headers);req.on("data", (chunk) => {console.log(chunk);});req.on("end", () => {console.log("获取完毕");});res.end("ok");
});server.listen(80, () => {console.log("服务启动成功:", "http://127.0.0.1:80");
});
4.3 获取请求体
使用 new URL(req.url, 'http://127.0.0.1') 拿到请求报文路径,pathname 输出路径,searchParams.get(key) 查询字符串
// 访问 127.0.0.1/login?username='yqcoder'&password=111
const http = require("http");const server = http.createServer((req, res) => {const url = new URL(req.url, "http://127.0.0.1");console.log(url.pathname); // /loginconsole.log(url.searchParams.get("username")); // yqcoderres.end("hello");
});server.listen(80, () => {console.log("服务启动成功:", "http://127.0.0.1:80");
});
4.4 设置响应报文
状态码 statusCode、状态描述 statusMessage、响应头 setHeader、响应体 write()、end(),一般我们在 write 里设置了响应体,就不会在 end 里传值了。
const http = require("http");const server = http.createServer((req, res) => {const url = new URL(req.url, "http://127.0.0.1");res.statusCode = 200;res.statusMessage = "成功";res.setHeader("Content-type", "text/html;charset=utf-8");res.write("name");res.end("你好");
});server.listen(80, () => {console.log("服务启动成功:", "http://127.0.0.1:80");
});
4.5 设置响应体
const http = require("http");
const fs = require("fs");const server = http.createServer((req, res) => {res.setHeader("content-type", "text/html;charset=utf-8");const html = fs.readFileSync(`${__dirname}/index.html`);res.end(html);
});server.listen(80, () => {console.log("服务启动成功:", "http://127.0.0.1:80");
});
4.6 网页资源加载基本过程
输入网址按回车后,服务器首先返回 html 资源,然后根据 html 中的外部链接,继续返回相应的静态资源。静态文件一般包括 js、css、图片、视频等。
静态资源请求的路径,是启动服务文件所在路径的相对路径。举个例子,html 文件外链了 css,js,png,mp4 等资源。启动服务获取这些外联资源。
搭建一个静态资源服务
const http = require("http");
const fs = require("fs");const server = http.createServer((req, res) => {res.setHeader("Content-type", "text/html;charset=utf-8");const { pathname } = new URL(req.url, "http://127.0.0.1");let filename = __dirname + pathname;fs.readFile(filename, (err, data) => {if (err) {res.statusCode = 500;res.end("文件读取失败");return;}res.end(data);});
});server.listen(80, () => {console.log(`服务启动成功:http://127.0.0.1`);
});
4.7 静态资源目录和网站根目录
静态资源存在的文件夹称之为静态资源目录也被称为网站根目录。
5. url 模块
写接口时,浏览器的请求路径和查询字符串是很重要的。所以需要使用 url 模块去提取我们所需要的路径信息。
5.1 解析路径
使用 url.parse(path[, boolean]) 解析请求路径,path 请求路径,boolean 查询字符串是否以对象展示。url.parse(path) 返回一个路径对象 result。通过 result.pathname 获取路径。result.query 获取查询字符串。
// 浏览器访问 http://127.0.0.1/admin/login?username=yqcoder&password=111
const http = require("http");
const url = require("url");const server = http.createServer((req, res) => {let result = url.parse(req.url, true);console.log(result);// Url {// protocol: null,// slashes: null,// auth: null,// host: null,// port: null,// hostname: null,// hash: null,// search: '?username=yqcoder&password=111',// query: [Object: null prototype] { username: 'yqcoder', password: '111' },// pathname: '/admin/login',// path: '/admin/login?username=yqcoder&password=111',// href: '/admin/login?username=yqcoder&password=111'// }res.end("hello man");
});
server.listen(80, () => {console.log("服务启动成功:", "http://127.0.0.1:80");
});
5.2 网页 URL 中的绝对路径
绝对路径可靠性强,而且容易理解,直接向目标资源发送请求,多用于网站的外链。
绝对路径有三种形式:
1. http://www.baidu.com/demo.png 完全体
2. //www.baidu.com/demo.png 与页面协议拼接
3. /demo.png 与页面协议、域名、端口拼接。
我们项目中静态资源多用于第三种形式,这样部署到不同的域名,也可以直接访问到。
5.3 网页 URL 中的相对路径
相对于当前文件夹的路径。需要与当前页面 URL 进行拼接。
相对路径有三种形式:
1. ./css/index.css 等于 css/index.css
2. ../js/index.js
3. ../../assets/demo.png
5.4 网页中 URL 使用场景
a 标签 href、link 标签 href、script 标签 src、img 标签的 src、video audio 标签的 src、form 标签的 action、AJAX 请求的 URL 等。
5.5 mime 类型
用来表示文档、文件、或字节流的性质和格式。格式 [type]/[subType]。
HTTP 服务设置响应头 Content-Type 来声明响应体的 MIME 类型。MIME 类型有:
html: "text/html";
css: "text/css";
js: "text/javescript";
png: "image/png";
jpg: "image/jpg";
gif: "image/gif";
mp4: "video/mp4";
mp3: "audio/mpeg";
json: "application/json";
对未知资源使用 application/octet-stream 类型。浏览器遇到该类型的响应时,会对响应体内容进行独立储存,也就是我们常见的下载效果。
5.6 解决乱码问题
中文乱码时,通过设置响应头 Content-Type: 'mime 类型;charset=utf-8'。
5.7 GET 和 POST 区别
GET 请求情况:地址栏输入 url、a 链接、link、script、video、audio、img、form 标签 method 为 get、ajax 中的 get
POST 请求情况:form 标签 post、AJAX 的 post 请求
主要区别:
作用:GET用于获取数据、POST 用于提交数据
参数位置:GET请求是一般将参数缀到 URL 之后、POST 请求一般是将参数放到请求体中
安全性:POST相较于GET安全一些。
大小限制:GET 传参一般是2K、POST 传参没限制
四. Express 框架
express 是一个封装好的开发框架,封装了很多功能,便于我们开发 WEB 应用。
1. express 初体验
安装依赖
npm install express
构建 server.js
const express = require("express");
const app = express();app.get("/home", (req, res) => {res.end("hello express");
});app.listen(3000, () => {console.log("服务启动成功.....");
});
2. 路由介绍
路由确定了客户端对特定端点的请求,路由构成 app.<method>(path, callback),请求方法、路径、回调函数。
app.get("/home", (req, res) => {res.end("hello");
});
3. 路由的使用
匹配 get 请求
app.get("/info", (req, res) => {});
匹配 post 请求
app.post("/login", (req, res) => {});
匹配 get/post 请求。
app.all("/login", (req, res) => {});
匹配所有请求,一般用于响应 404 页面
app.all("*", (req, res) => {});
4. 获取请求报文
express 封装了一些 API 快速获取请求报文,req.path 路径、req.query 请求参数、req.ip 客户端 ip
// 例子 /login?username=yqcoder&password=111
app.post("/login", (req, res) => {req.path; // /loginreq.query; // { username: 'yqcoder', password: '111' }req.ip; // 127.0.0.1
});
5. 获取路由参数
类似京东详情页 10086.html、10011.html 等,我们使用占位符 id 去匹配这种客户端请求。通过 req.params.id 拿到。
app.get("/:id.html", (req, res) => {// 获取 URL 路由参数req.params.id; //res.end("成功");
});
6. 响应设置
express 封装了一些 API 快速设置响应,并且可以链式设置。res.status 设置状态,res.set 设置响应头,res.send 设置响应体
app.get("/login", (req, res) => {res.status(200).set("abc", "123").send("这是OK");
});
7. 其他响应
可以对 http 请求做出其他响应。redirect 重定向,download 下载响应,json 响应 json,sendFile 响应文件内容
app.get("/login", (req, res) => {// 重定向;res.rediect("http://www.baidu.com");// 下载响应res.download(__dirname + "/demo.mp4");// JSON 响应res.json({name: "yqcoder",slogan: "yyyyy",});// 响应文件内容res.sendFile(__dirname + "/test.html");
});
8. 中间件
8.1 什么是中间件
中间件本质是一个回调函数,可以像路由回调一样访问请求对象(req)、响应对象(res)
8.2 中间件的作用
封装公共操作,简化代码
8.3 中间件的类型
中间件类型分为:全局中间件、路由中间件、静态资源中间件
全局中间件:对所有请求做处理
function recordMiddleware(req, res, next) {let { ip, url } = req;console.log(ip, url);next();
}app.use(recordMiddleware);
路由中间件:只对特定路由做处理
function checkCodeMiddleware(req, res, next) {if (req.query.code === "521") {next();} else {res.send("无权限");}
}app.get("/menu", checkCodeMiddleware, (req, res) => {res.send("后台首页");
});
静态资源中间件:express 内置的中间件,设置静态资源请求的路径。
// dirname + '/public' 这是静态资源文件夹路径
app.use(express.static(__dirname + "/public"));
静态资源中间件注意事项:
1. index.html 为默认打开资源。
2. 静态资源与路由同时匹配,谁先匹配谁就响应。
3. 路由响应动态资源,静态资源中间件响应静态资源。