Nodejs(含js模块化+npm+express)
1. 简介
1.1 运行环境
浏览器是 js 的前端运行环境
Node.js 是 js 的后端运行环境
Node.js 中无法调用 DOM 和 BOM 等浏览器内置 API

1.2 Node.js 可以做什么
基于 Express 框架可以快速构建 Web 应用
基于 Electron 框架可以快速构建跨平台的桌面应用
基于 restify 框架可以快速构建 API 接口项目
读取和操作数据库,创建实用的命令行工具辅助前端开发
...
1.3 安装与运行
下载稳定版node.js
安装完查看 node.js 的版本
node -v
- 创建测试文件,通过命令行运行(需要切换到文件所在目录)
node test.js

2. fs 文件系统模块
fs 模块是 Node.js 官方提供的用来操作文件的模块,提供了一系列的方法和属性,用来满足用户对文件的操作需求
- 如果要在 js 代码中使用 fs 模块来操作文件,则需要先导入
const fs = require("fs");
2.1 读取指定文件中的内容
- 使用
fs.readFile()读取指定文件中的内容
fs.readFile(path[, options), callback)
参数解读
path:必选,读取的文件路径(字符串)options:可选,以什么编码格式来读取文件,默认指定utf8callback:必选,文件读取完成后,通过回调函数拿到读取的失败和成功的结果,err 和 dataObj
示例:
const fs = require("fs");
fs.readFile("./files/1.txt", "utf-8", function (err, dataObj) {
// 读取成功,err为null,否则为错误对象。因此能以此进行判断
if (err) {
return console.log("文件读取失败!" + err.message);
}
// 读取成功的结果,失败则为undefined
console.log("文件读取成功,内容是:" + dataObj);
});

2.2 向指定文件中写入内容
- 使用
fs.writeFile()向指定文件写入内容
fs.writeFile(file, data[, options], callback)
参数解读
file:必选,文件存放的路径(字符串)data:必选,要写入的内容options:可选,以什么格式写入文件内容,默认utf8callback:必选,文件写入完成后的回调函数
示例
const fs = require("fs");
fs.writeFile("F:/files/2.txt", "hello world", function (err) {
// 写入成功,err为null,否则为错误对象
if (err) {
return console.log("写入文件失败!" + err.message);
}
console.log("文件写入成功!");
});


2.3 小练习
需求:整理
成绩.txt中的数据,并写入成绩-ok.txt源数据与期望格式数据如下:

- 代码实现
const fs = require("fs");
fs.readFile("./files/成绩.txt", function (err, dataObj) {
if (err) {
return console.log("文件读取失败!" + err.message);
}
let dataStr = dataObj.toString();
dataStr = dataStr.replaceAll("=", ":");
dataStr = dataStr.replaceAll(" ", "\n");
fs.writeFile("./files/成绩-ok.txt", dataStr, function (err) {
if (err) {
return console.log("文件写入失败!" + err.message);
}
});
});
2.4 路径动态拼接的问题
在使用 fs 模块操作文件时,如果使用相对路径,很容易出现动态路径拼接错误的问题
原因:代码在运行时,会以执行 node 命令所处的目录,动态拼接出被操作文件的完整路径
解决
提供完整路径:移植性差,不利于维护
使用
__dirname_ + '/files/data.txt':__dirname表示当前文件所在的目录
使用相对路径,并在文件所在目录上一级执行命令

- 优化后的代码
const fs = require("fs");
fs.readFile(__dirname + "/files/data.txt", function (err, dataObj) {
if (err) {
return console.log("文件读取失败!" + err.message);
}
console.log(dataObj.toString());
});

3. Path 路径模块
path 模块是 Node.js 官方提供的用来处理路径的模块。它提供了一系列的方法和属性,用来满足用户对路径的处理需求
- 如果要在 js 代码中使用 path 模块来处理路径,则需要先导入
const path = require("path");
3.1 路径拼接
- 使用
path.join()把多个路径片段拼接为完整的路径字符串
path.join([...paths]);
参数解读
...paths<string>:路径片段的序列返回值:
<string>
示例
const fs = require("fs");
const path = require("path");
// ../ 会抵消一级路径
const pathStr = path.join("/a", "/b/c", "../", "./d", "e");
console.log(pathStr);
fs.readFile(path.join(__dirname, "/files/data.txt"), function (err, dataObj) {
if (err) {
return console.log("文件读取失败!" + err.message);
}
console.log(dataObj.toString());
});
- 注:以后涉及路径拼接的操作,都要用
path.join()进行处理,如果直接使用+进行拼接,可能会有问题,如下图所示

3.2 获取路径中的文件名
- 使用
path.basename()方法获取路径中的最后一部分,经常用它获取路径中的文件名
path.basename(path[, ext])
参数解读
path:必选,表示一个路径的字符串ext:可选,表示文件扩展名返回值:表示路径中的最后一部分
示例
const path = require("path");
// 不加第二个参数,会连扩展名一起输出
const fileName = path.basename("/a/b/c/index.html", ".html");
console.log(fileName);

3.3 获取路径中的文件扩展名
- 使用
path.extname()获取路径中的扩展名
const path = require("path");
const extName = path.extname("/a/b/c/index.html");
console.log(extName);
参数解读
path:必选,表示路径字符串返回值:扩展名字符串
示例
const path = require("path");
const extName = path.extname("/a/b/c/index.html");
console.log(extName);
3.4 小练习
需求:将
Clock.html拆分为三个文件,clock/index.html、clock/index.js、clock/index.css,并引入 css、js 文件(找一个含 html、css、js 的文件进行练习即可)思路
设置正则表达式匹配
<style></style>和<script></script>中的内容使用 fs 模块读取
Clock.html文件编写三个方法处理 css、js、html 内容写入文件中
目录结构

- 代码实现
const fs = require("fs");
const path = require("path");
// 先设置正则表达式,提取<style></style>和<script></script>的内容
const regStyle = /<style>[\s\S]*<\/style>/;
const regScript = /<script>[\s\S]*<\/script>/;
// 读取html文件
fs.readFile(path.join(__dirname, "../clockHtml/Clock.html"), function (err, dataObj) {
if (err) return console.log("文件读取失败!" + err.message);
// 读取文件成功,调用三个方法将内容拆分成三个文件
resolveCss(dataObj.toString());
resolveJs(dataObj.toString());
resolveHtml(dataObj.toString());
});
// 处理css
function resolveCss(htmlStr) {
const cssStr = regStyle.exec(htmlStr);
cssStr[0] = cssStr[0].replace("<style>", "").replace("</style>", "");
fs.writeFile(path.join(__dirname, "./clock/index.css"), cssStr[0], function (err) {
if (err) return console.log("文件写入失败!" + cssStr);
});
console.log("css文件写入成功!");
}
// 处理js
function resolveJs(htmlStr) {
const jsStr = regScript.exec(htmlStr);
jsStr[0] = jsStr[0].replace("<script>", "").replace("</script>", "");
fs.writeFile(path.join(__dirname, "./clock/index.js"), jsStr[0], function (err) {
if (err) return console.log("文件写入失败!" + jsStr);
});
console.log("js文件写入成功!");
}
// 处理html
function resolveHtml(htmlStr) {
const newStr = htmlStr
.replace(regStyle, '<link rel="stylesheet" href="index.css">')
.replace(regScript, '<script src="index.js"></script>');
fs.writeFile(path.join(__dirname, "./clock/index.html"), newStr, function (err) {
if (err) console.log("文件写入失败!" + err.message);
console.log("html文件写入成功!");
});
}
两个注意点
fs.writeFile()只能用来创建文件,不能用来创建路径重复调用
fs.writeFile()写入同一个文件,新写入的内容会覆盖之前的内容
4. http 模块
4.1 简介
http 模块是 Node.js 官方提供的用来创建 web 服务器的模块
客户端:在网络节点中,负责消费资源的电脑
服务器:负责对外提供网络资源的电脑
服务器和普通电脑的区别在于:服务器上安装了 web 服务器软件,如 IIS、Apache 等,通过安装这些服务器软件,就能把一台普通的电脑变成一台 web 服务器
在 Node.js 中不需要使用 IIS、Apache 等第三方 web 服务器软件,可以基于 Node.js 提供的 http 模块轻松手写一个服务器软件
4.2 创建最基本的 web 服务器
- 导入
const http = require("http");
- 调用
http.createServer()创建 web 服务器实例
const server = http.createServer();
- 为服务器实例绑定
request事件,监听客户端的请求
server.on("request", (req, res) => {
// 只要有客户端来请求服务器,就会触发request事件,从而调用这个事件处理函数
console.log("Someone visit our web server.");
});
- 调用
listen()启动当前 web 服务器实例
server.listen("8080", () => {
console.log("http server running at http://127.0.0.1:8080");
});
- 运行之后用浏览器访问该地址

4.3 req 请求对象
只要服务器收到了客户端的请求,就会调用通过
server.on()为服务器绑定的request事件处理函数req是请求对象,包含了与客户端相关的数据和属性req.url:客户端请求的 url 地址req.method:客户端的 method 请求类型
示例
const http = require("http");
const server = http.createServer();
server.on("request", (req, res) => {
console.log(`Your request url is ${req.url}, and request method is ${req.method}`);
});
server.listen("8080", () => {
console.log("http server running at http://127.0.0.1:8080");
});
4.4 res 响应对象
res是响应对象,包含与服务器相关的数据和属性res.end():向客户端发送指定的内容,并结束本次请求的处理过程
示例
const http = require("http");
const server = http.createServer();
server.on("request", (req, res) => {
const str = `Your request url is ${req.url}, and request method is ${req.method}`;
res.end(str);
});
server.listen("8080", () => {
console.log("http server running at http://127.0.0.1:8080");
});

- 通过一些接口测试软件测试一下其他请求方式,此处使用
Apifox

4.5 解决中文乱码问题

- 当调用
res.end()向客户端发送中文内容时,会出现乱码,此时需要手动设置内容的编码格式
res.setHeader("Content-Type", "text-html; charset=utf-8");
- 示例
const http = require("http");
const server = http.createServer();
server.on("request", (req, res) => {
const str = `您的请求地址是:${req.url},请求方式是:${req.method}`;
res.setHeader("Content-Type", "text/html; charset=utf-8");
res.end(str);
});
server.listen("8080", () => {
console.log("http server running at http://127.0.0.1:8080");
});

4.6 小练习
4.6.1 根据不同的 url 响应不同的 html 内容
实现步骤
获取请求的 url
路径为
/或/index.html,访问的是首页路径为
/about.html,访问的是关于页面其他则显示
404 Not Found设置
Content-Type响应头,防止中文乱码使用
res.end()响应给客户端
代码实现
const http = require("http");
const server = http.createServer();
server.on("request", (req, res) => {
let content = "<h1>404 Not Found</h1>";
console.log(req.url);
if (req.url === "/" || req.url === "/index.html") {
content = "<h1>首页</h1>";
} else if (req.url === "/about.html") {
content = "<h1>关于</h1>";
}
res.setHeader("Content-Type", "text/html; charset=utf-8");
res.end(content);
});
server.listen("8080", () => {
console.log("http server running at http://127.0.0.1:8080");
});
4.6.2 实现时钟的 web 服务器
- 思路:把文件的实际存放路径,作为每个资源的请求 url 地址

- 代码实现
const http = require("http");
const fs = require("fs");
const path = require("path");
const server = http.createServer();
server.on("request", (req, res) => {
if (req.url !== "/favicon.ico") {
fs.readFile(path.join(__dirname, req.url), function (err, dataObj) {
if (err) {
return res.end(`<h1>404 Not Found</h1>`);
}
res.end(dataObj.toString());
});
}
});
server.listen("8080", () => {
console.log("http server running at http://127.0.0.1:8080");
});
优化资源请求路径
访问
/时默认也访问/clock/index.html简化路径输入
/clock/index.html-->/index.html
const http = require("http");
const fs = require("fs");
const path = require("path");
const server = http.createServer();
server.on("request", (req, res) => {
// 优化资源请求路径
let fpath = "";
if (req.url === "/") {
fpath = path.join(__dirname, "./clock/index.html");
} else {
fpath = path.join(__dirname, "/clock", req.url);
}
if (req.url !== "/favicon.ico") {
fs.readFile(fpath, function (err, dataObj) {
if (err) {
return res.end(`<h1>404 Not Found</h1>`);
}
res.end(dataObj.toString());
});
}
});
server.listen("8080", () => {
console.log("http server running at http://127.0.0.1:8080");
});
5. js 模块化规范
5.1 模块化概述
5.1.1 什么是模块化
将程序⽂件依据⼀定规则拆分成多个⽂件,这种编码⽅式就是模块化的编码方式
拆分出来每个⽂件就是⼀个模块,模块中的数据都是私有的,模块之间互相隔离
同时也能通过一些手段,可以把模块内的指定数据“交出去”,供其他模块使用
5.1.2 为什么需要模块化
随着应用的复杂度越来越高,其代码量和文件数量都会急剧增加,会逐渐引发以下问题:
全局污染问题
依赖混乱问题
数据安全问题
好处
复用性
可维护性
可实现按需加载
5.2 有哪些模块化规范
CommonJS——服务端应用广泛
AMD(了解)
CMD(了解)
ES6 模块化——浏览器端应用广泛
5.3 导入和导出的概念
模块化的核心思想就是:模块之间是隔离的,通过导入和导出进行数据和功能的共享
导出(暴露):模块公开其内部的⼀部分(如变量、函数等),使这些内容可以被其他模块使用
导入(引入):模块引入和使用其他模块导出的内容,以重用代码和功能

5.4 Node.js 中的模块化
5.4.1 分类
根据来源的不同,分为三大类
内置模块:如 fs、path、http 等
自定义模块:用户创建的每个
.js文件都是自定义模块第三方模块:由第三方开发出来的模块,使用前需要提前下载
5.4.2 加载模块
// 1、加载内置的fs模块
const fs = require("fs");
// 2、加载自定义模块,.js后缀可省略
const custom = require("./custom.js");
// 3、加载第三方模块
const moment = require("moment");
5.4.3 模块作用域与 module 对象
模块作用域:只能在当前模块内被访问
好处:防止全局变量污染问题
module 对象:每个
.js自定义模块中都有一个module对象,里面存储了和当前模块有关的信息

5.5 CommonJS 规范
Node.js 遵循了 CommonJS 模块化规范,CommonJS 规定了模块的特性和各模块之间如何相互依赖
CommonJS 规定
每个模块内部,module 变量代表当前模块
module 变量是一个对象,其
exports属性(即module.exports)是对外的接口加载某个模块,其实就是加载该模块的
module.exports属性,require()方法用于加载模块
5.5.1 初步体验
- school.js
const name = "尚硅谷";
const slogan = "让天下没有难学的技术!";
function getTel() {
return "010-56253825";
}
function getCities() {
return ["北京", "上海", "深圳", "成都", "武汉", "西安"];
}
// 通过给exports对象添加属性的方式,来导出数据
// 此处不导出getCities
exports.name = name;
exports.slogan = slogan;
exports.getTel = getTel;
- student.js
const name = "张三";
const motto = "相信明天会更好!";
function getTel() {
return "13877889900";
}
function getHobby() {
return ["抽烟", "喝酒", "烫头"];
}
// 通过给exports对象添加属性的方式,来导出数据
// 此处不导出getHobby
exports.name = name;
exports.motto = motto;
exports.getTel = getTel;
- index.js
// 引入school模块暴露的所有内容
const school = require("./school.js");
// 引入student模块暴露的所有内容
const student = require("./student.js");
console.log(school);
console.log(student);
5.5.2 导出数据
在
CommonJS标准中,导出数据有两种方式:第一种方式:
module.exports = value第二种方式:
exports.name = value
注:
- 每个模块内部的:
this、exports、modules.exports在初始时,都指向同一个空对象,该空对象就是当前模块导出的数据,如下图:

无论如何修改导出对象,最终导出的都是
module.exports的值exports是对module.exports的初始引用,仅为了方便给导出添加属性,所以不能用exports={}的形式导出数据,但是可以用module.exports={}导出数据注:为了防止混乱,建议不要在同一模块中同时使用
exports和module.exports
- 每个模块内部的:
school.js
const name = "尚硅谷";
const slogan = "让天下没有难学的技术!";
function getTel() {
return "010-56253825";
}
function getCities() {
return ["北京", "上海", "深圳", "成都", "武汉", "西安"];
}
module.exports = { name, slogan, getTel };
// this.c =789
// exports = {a:1}
// exports.b = 2
// module.exports.c = 3
// module.exports = {d:4} // 最终导出成功的是这个
// console.log(this)
// console.log(exports)
// console.log(module.exports)
// console.log(this === exports && exports === module.exports)
exports.name = name;
exports.slogan = slogan;
exports.getTel = getTel;
解释
一开始
module.exports和exports指向同一个空对象exports = {a:1}:exports就指向了{a:1}这个新对象,module.exports仍指向空对象exports.b = 2:向exports指向的对象添加属性bmodule.exports.c = 3:向module.exports指向的对象添加属性cmodule.exports = {d:4}:module.exports指向了新对象{d:4}无论如何修改导出对象,最终导出的都是
module.exports的值

5.5.3 导入数据
在 CJS 模块化标准中,使用内置的 require 函数进行导入数据
//直接引入模块
const school = require("./school.js");
//引入同时解构出要用的数据
const { name, slogan, getTel } = require("./school.js");
//引入同时解构+重命名
const { name: stuName, motto, getTel: stuTel } = require("./student.js");
5.5.4 扩展理解
- 一个 JS 模块在执行时,是被包裹在一个内置函数中执行的,所以每个模块都有自己的作用域,可以通过如下方式验证这一说法:
console.log(arguments);
console.log(arguments.callee.toString());
- 内置函数的大致形式如下:
function (exports, require, module, __filename, __dirname){
/**************************/
}
5.5.5 浏览器端运行
Node.js 默认是支持 CommonJS 规范的,但浏览器端不支持,所以需要经过编译,步骤如下:
- 第一步:全局安装 browserify
npm i browserify -g- 第二步:编译
browserify index.js -o build.js注:index.js 是源文件,build.js 是输出的目标文件
第三步:页面中引入使用
<script type="text/javascript" src="./build.js"></script>
5.6 ES6 模块化规范
ES6 模块化规范是一个官方标准的规范,它是在语言标准的层面上实现了模块化功能,是目前最流行的模块化规范,且浏览器与服务端均支持该规范
5.6.1 初步体验
- school.js
// 导出name
export const name = "尚硅谷";
// 导出slogan
export const slogan = "让天下没有难学的技术!";
// 导出getTel
export function getTel() {
return "010-56253825";
}
function getCities() {
return ["北京", "上海", "深圳", "成都", "武汉", "西安"];
}
- student.js
export const name = "张三";
export const motto = "相信明天会更好!";
export function getTel() {
return "13877889900";
}
function getHobby() {
return ["抽烟", "喝酒", "烫头"];
}
- index.js
// 引入school模块暴露的所有内容
import * as school from "./school.js";
// 引入student模块暴露的所有内容
import * as student from "./student.js";
- 页面中引入 index.js
<script type="module" src="./index.js"></script>
5.6.2 Node 中运行 ES6 模块
Node.js 中运行 ES6 模块代码有两种方式:
方式一:将 JavaScript 文件后缀从
.js改为.mjs,Node 则会自动识别 ES6 模块方式二:在
package.json中设置type属性值为module

5.6.3 导出数据
ES6 模块化提供 3 种导出方式:① 分别导出、② 统一导出、③ 默认导出
- 分别导出
// 导出name
export const name = "尚硅谷";
// 导出slogan
export const slogan = "让天下没有难学的技术!";
// 导出getTel
export function getTel() {
return "010-56253825";
}
- 统一导出
const name = "尚硅谷";
const slogan = "让天下没有难学的技术!";
function getTel() {
return "010-56253825";
}
function getCities() {
return ["北京", "上海", "深圳", "成都", "武汉", "西安"];
}
// 统一导出了:name、slogan、getTel
export { name, slogan, getTel };
- 默认导出
const name = "尚硅谷";
const slogan = "让天下没有难学的技术!";
function getTel() {
return "010-56253825";
}
function getCities() {
return ["北京", "上海", "深圳", "成都", "武汉", "西安"];
}
//默认导出了:name、slogan、getTel
export default { name, slogan, getTel };
- 注:上述多种导出方式,可以同时使用
// 导出name —— 分别导出
export const name = "尚硅谷";
const slogan = "让天下没有难学的技术!";
function getTel() {
return "010-56253825";
}
function getCities() {
return ["北京", "上海", "深圳", "成都", "武汉", "西安"];
}
// 导出slogan —— 统一导出
export { slogan };
// 导出getTel —— 默认导出
export default getTel;
5.6.4 导入数据
对于 ES6 模块化来说,使用何种导入方式,要根据导出方式决定
?️ 导入全部(通用)
- 可以将模块中的所有导出内容整合到一个对象中
import * as school from "./school.js";
?️ 命名导入(对应到处方式:分别导出、统一导出)
- 导出数据的模块
// 分别导出
export const name = "尚硅谷";
// 分别导出
export const slogan = "让天下没有难学的技术!";
function getTel() {
return "010-56253825";
}
function getCities() {
return ["北京", "上海", "深圳", "成都", "武汉", "西安"];
}
// 统一导出
export { getTel };
- 命名导入
import { name, slogan, getTel } from "./school.js";
- 通过
as重命名
import { name as myName, slogan, getTel } from "./school.js";
?️ 默认导出(对应导出方式:默认导出)
- 导出数据的模块
const name = "尚硅谷";
const slogan = "让天下没有难学的技术!";
function getTel() {
return "010-56253825";
}
function getCities() {
return ["北京", "上海", "深圳", "成都", "武汉", "西安"];
}
// 默认导出了:name、slogan、getTel
export default { name, slogan, getTel };
- 默认导入
import school from "./school.js"; // 默认导出的名字可以修改,不是必须为school
?️ 命名导入与默认导入可以混合使用
- 导出数据的模块
// 分别导出
export const name = "尚硅谷";
// 分别导出
export const slogan = "让天下没有难学的技术!";
function getTel() {
return "010-56253825";
}
function getCities() {
return ["北京", "上海", "深圳", "成都", "武汉", "西安"];
}
// 默认导出
export default getTel;
- 命名导入与默认导入混合使用,且默认导入的内容必须放在前方
import getTel, { name, slogan } from "./school.js";
?️ 动态导入(通用)
- 允许在运行时按需加载模块,返回值是一个 Promise
const school = await import("./school.js");
console.log(school);
?️import 可以不接收任何数据
- 例如只是让 mock.js 参与运行
import "./mock.js";
5.6.5 数据引用问题
- 思考1:如下代码的输出结果是什么?
function count() {
let sum = 1;
function increment() {
sum += 1;
}
return { sum, increment };
}
const { sum, increment } = count();
console.log(sum); // 1
increment();
increment();
console.log(sum); // 1
思考2:使用 CommnJS 规范,编写如下代码,输出结果是什么?
count.js
let sum = 1;
function increment() {
sum += 1;
}
module.exports = { sum, increment };
- index.js
const { sum, increment } = require("./count.js");
console.log(sum); // 1
increment();
increment();
console.log(sum); // 1
说明:cjs 导入的变量是复制品,无论调用的函数怎么修改,改的还是模块内部的变量
思考3:使用 ES6 模块化规范,编写如下代码,输出结果是什么?
count.js
let sum = 1;
function increment() {
sum += 1;
}
export { sum, increment };
- index.js
import { sum, increment } from "./count.js";
console.log(sum); // 1
increment();
increment();
console.log(sum); // 3
说明:es6 导入的变量和模块中的变量公用同一块内存,因此会修改变量的值
使用原则:导出的常量,务必使用
const定义
6. 包与 npm
6.1 简介
包:Node.js 中的第三方模块
包的来源:由第三方个人或团队开发出来的,免费供所有人使用(免费开源)
为什么需要包
Node.js 的内置模块仅提供一些底层的 API,在基于内置模块进行项目开发时效率较低
包是基于内置模块封装出来的,提供了更高级、更方便的 API,极大提高了开发效率
从哪下载包
搜索需要的包:npmjs
从https://registry.npmjs.org/服务器上下载自己需要的包
如何下载
- 包管理工具 npm:Node Package Manager
6.2 安装包
# 完整写法,默认下载最新版的包
npm install 包名
# 简写
npm i 包名
# 安装指定版本的包
npm i 包名@2.22.2
安装完后,查看文档学习该模块的使用方法
示例:安装
moment对时间进行格式化
const moment = require("moment");
const datetime = moment().format("YYYY-MM-DD HH:MM:SS");
console.log(datetime);
初次装包完成后,项目文件夹下多了
node_modules文件夹和package-lock.json的配置文件其中
node_modules文件夹用来存放所有已安装到项目中的包,require()就是从这个目录中查找并加载包package-lock.json配置文件用来记录node_modules目录下的每一个包的下载信息,如包名、版本号、下载地址等
注:不要手动修改
node_modules或package-lock.js文件中的任何代码,npm 包管理工具会自动维护它们
6.3 包的语义化版本规范
包的版本号是以“点分十进制”形式进行定义的,总共三位数字,例如:2.24.0
其中每一位数字所代表的含义如下:
第 1 位数字:大版本,当发生了底层重构时,大版本+1
第 2 位数字:功能版本,当新增了一些功能时,功能版本+1
第 3 位数字:Bug 修复版本,对 bug 进行修复后,bug 修复版本+1
版本号提升规则:只要前面的版本号增长了,则后面的版本号归零
6.4 包管理配置文件
npm 规定,在项目根目录中,必须提供名为
package.json的包管理配置文件,用来记录与项目有关的一些配置信息,如:
项目名称、版本号、描述等
项目中都用到了哪些包
哪些包只在开发期间会用到
哪些包在开发和部署时都需要用到
多人协作的问题
整个项目的体积是 30.4M,第三方包的体积是 28.8M,项目源代码的体积 1.6M
问题:第三方包体积过大,不方便团队成员之间共享项目源代码
解决:共享时剔除
node_modules
如何记录项目中安装了哪些包
在项目根目录中,创建
package.json配置文件,即可用来记录项目中安装了哪些包,从而方便剔除node_modules目录后,在团队成员之间共享项目的源代码- 注:在项目开发中,一定要把
node_modules文件夹添加到.gitignore忽略文件中
- 注:在项目开发中,一定要把
快速创建
package.json
npm init -y
说明
在执行命令所处的目录中,快速新建
package.json文件还未写任何代码前先创建该文件
该命令只能在英文的目录下成功运行,不能含中文、空格
运行
npm install 包名时,npm 包管理工具会自动把包的名称和版本号记录到package.json中
6.4.1 dependencies 节点
package.json文件中有一个dependencies节点,专门用来记录用npm install安装了哪些包一次性安装所有包
当拿到一个剔除了
node_modules的项目后,需要先把所有的包下载到项目中,项目才能运行起来执行
npm install命令时,npm 包管理工具会先读取package.json中的dependencies节点读取到记录的所有依赖包名称和版本号后,npm 包管理工具会把这些包一次性下载到项目中
npm install
npm i
6.4.2 devDependencies 节点
如果某些包只在项目开发阶段会用到,在项目上线之后不会用到,则建议把这些包记录到
devDependencies节点中如果在开发和项目上线之后都需要用到,则建议把这些包记录到
dependencies节点中使用如下命令安装指定包,并记录到
devDependencies节点中
# 简写
npm i 包名 -D
# 完整写法
npm install 包名 --save-dev

6.5 卸载包
npm uninstall 包名
- 注:
npm uninstall执行成功后,会把卸载的包自动从package.json的dependencies中移除
6.6 解决下包速度慢的问题
为什么下载速度慢
- 在使用 npm 下包时,默认从国外的服务器进行下载,此时,网络数据的传输需要经过漫长的海底光缆,因此下包速度会很慢
npm 镜像服务器
淘宝在国内搭建了一个服务器,专门把国外官方服务器上的包同步到国内的服务器,并在国内提供下包的服务,从而提高了下包的速度
扩展:镜像是一种文件存储形式,一个磁盘上的数据在另一个磁盘上存在一个完全相同的副本即为镜像

- 切换 npm 的下包镜像源
# 查看当前的下包镜像源
npm config get registry
# 切换镜像源,选择一个即可
npm config set registry https://registry.npmmirror.com # 淘宝
npm config set registry https://npm.aliyun.com # 阿里云
npm config set registry http://mirrors.cloud.tencent.com/npm/ # 腾讯云
npm config set registry https://mirrors.huaweicloud.com/repository/npm/ # 华为云
# 检查镜像源是否切换成功
npm config get registry

nrm- 为了更方便的切换下包的镜像源,可以安装
nrm工具,利用其提供的终端命令,可以快速查看和切换下包的镜像源
- 为了更方便的切换下包的镜像源,可以安装
# 将nrm安装为全局可用的工具
npm i nrm -g
# 查看所有可用的镜像源
nrm ls
# 将下包的镜像源切换为淘宝镜像
nrm use taobao

6.7 包的分类
分为两大类
项目包
开发依赖包:被记录到
devDependencies节点中的包,只在开发期间会用到核心依赖包:被记录到
dependencies节点中的包,在开发期间和项目上线之后都会用到
全局包
执行
npm install使用了-g参数全局包会被安装到
C:\User\用户目录\AppData\Roaming\npm\node_modules目录下
npm i 包名 -g # 全局安装指定的包 npm uninstall 包名 -g # 卸载全局安装的包注:
只有工具性质的包才有全局安装的必要性,因为它们提供了好用的终端命令
判断某个包是否需要全局安装才能使用,可以参考官方提供的使用说明
以
i5ting_toc工具进行示例,它是一个可以把md文档转为html页面的小工具
# 将i5ting_toc安装为全局包
npm install -g i5ting_toc
# 调用i5ting_toc,轻松实现md转html的功能
# -o是转换成功后以默认浏览器打开
i5ting_toc -f 要转换的md文件路径 -o



6.8 规范的包结构
一个规范的包,其组成结构必须符合以下3点要求
包必须以单独的目录存在
包的顶级目录下必须包含
package.json这个包管理配置文件package.json中必须包含name,version,main这三个属性,分别代表包的名字、版本号、包的入口
注:以上3点要求是一个规范的包结构必须遵守的格式,关于更多约束可以参考https://classic.yarnpkg.com/en/docs/package-json
6.9 开发属于自己的包
需求:
格式化日期
转义 HTML 中的特殊字符
还原 HTML 中的特殊字符
初始化包的基本结构
新建
my-tools文件夹,作为包的根目录在
my-tools文件夹中,新建如下三个文件package.json:包管理配置文件index.js:包的入口文件README.md:包的说明文档
初始化
package.json
{
"name": "my-tools",
"version": "1.0.0",
"main": "index.js",
"description": "提供了格式化时间,HTMLEscape的功能",
"keywords": ["dateFormat", "escape"],
"license": "ISC"
}
关于更多
license许可协议相关的内容,可参考https://www.jianshu.com/p/86251523e898在
index.js中定义格式化时间的方法
// 包的入口文件
function dateFormat(datetime) {
const date = new Date(datetime);
const y = date.getFullYear();
const m = addZero(date.getMonth() + 1);
const d = addZero(date.getDate());
const hh = addZero(date.getHours());
const mm = addZero(date.getMinutes());
const ss = addZero(date.getSeconds());
return `${y}-${m}-${d} ${hh}:${mm}:${ss}`;
}
function addZero(n) {
return n > 9 ? n : "0" + n;
}
module.exports = { dateFormat };
test.js测试一下模块是否可以使用
const myTools = require("./my-tools");
const datetime = myTools.dateFormat(new Date());
console.log(datetime);

- 在
index.js中定义转义 HTML 的方法
function HTMLEscape(htmlStr) {
return htmlStr.replace(/<|>|"|&/g, match => {
switch (match) {
case "<":
return "<";
case ">":
return ">";
case '"':
return """;
case "&":
return "&";
}
});
}
- 在
index.js中定义还原 HTML 的方法
function htmlUnEscape(htmlStr) {
return htmlStr.replace(/<|>|"|&/g, match => {
switch (match) {
case "<":
return "<";
case ">":
return ">";
case """:
return '"';
case "&":
return "&";
}
});
}
将不同的功能进行模块化拆分
将格式化时间的功能拆分到
src/dateFormat.js中将处理 HTML 字符串的功能,拆分到
src/htmlEscape.js在
index.js中,导入两个模块,得到需要向外共享的方法在
index.js中,使用module.exports把对应的方法共享出去(解构)
index.js
// 包的入口文件
const date = require("./src/dateFormat");
const htmlEscape = require("./src/htmlEscape");
module.exports = {
...date,
...htmlEscape,
};
- 测试
const myTools = require("./my-tools");
const datetime = myTools.dateFormat(new Date());
console.log(datetime);
const htmlStr = "<h1 ttile='abc'>这是h1标签<span>123 </span></h1>";
const str = myTools.HTMLEscape(htmlStr);
const newStr = myTools.htmlUnEscape(str);
console.log(newStr);

编写包的说明文档
能清晰地将包的作用、用法、注意事项等描述清楚即可
以下
README.md包含以下内容- 安装方式、导入方式、格式化时间、转义 HTML 中的特殊字符、还原 HTML 中的特殊字符、开源协议
# 安装
npm i my-tools
# 导入
const myTools = require('./my-tools')
# 格式化时间
```
// 格式:YYYY-MM-DD hh:mm:ss
const datetime = myTools.dateFormat(new Date())
console.log(datetime)
```
# 转义 HTML 中的特殊字符
```
const htmlStr = "<h1 ttile='abc'>这是h1标签<span>123 </span></h1>"
// 结果:<h1 ttile='abc'>这是h1标签<span>123&nbsp;</span></h1>
const str = myTools.HTMLEscape(htmlStr)
```
# 还原 HTML 中的特殊字符
```
const htmlStr = "<h1 ttile='abc'>这是h1标签<span>123 </span></h1>"
const str = myTools.HTMLEscape(htmlStr)
const newStr = myTools.htmlUnEscape(str)
console.log(newStr)
```
# 开源协议
ISC
6.10 发布包
注册 npm 账号
登录 npm 账号
在终端执行
npm login命令注意,不是在官网登录,而是在命令行
在运行
npm login之前,必须先把下包的服务器地址切换为 npm 官方服务器,否则会导致发布包失败
切换到包的根目录,运行
npm publish,即可将包发布到 npm 上(注:包名不能雷同)删除已发布的包
npm unpublish 包名 --force注:
只能删除 72h 以内发布的包
删除后 24h 内不允许重复发布
发布包时要谨慎,尽量不要往 npm 上发布没有意义的包!




6.11 模块的加载机制
6.11.1 优先从缓存中加载
模块在第一次加载后会被缓存,即多次调用
require()不会导致模块的代码被执行多次注:不论是内置模块、用户自定义模块还是第三方模块,都会优先从缓存中加载,从而提高模块的加载效率

6.11.2 内置模块的加载机制
内置模块的加载优先级最高
如:
require('fs')始终返回内置的 fs 模块,即使node_modules目录下有同名包 fs
6.11.3 自定义模块的加载机制
使用
require()加载自定义模块时,必须指定以./或../开头的路径标识符,在加载自定义模块时,如果没有指定./或../这样的路径标识符,node 会把它当作内置模块或第三方模块进行加载在使用
require()导入自定义模块时,若省略了文件的扩展名,则 Node.js 会按顺序分别尝试加载以下文件按照确切的文件名进行加载
补全
.js扩展名进行加载补全
.json扩展名进行加载补全
.node扩展名进行加载加载失败,终端报错



6.11.4 第三方模块的加载机制
如果传递给
require()的模块标识符不是一个内置模块,也没有./或../开头,则 Node.js 会从当前模块的父目录开始,尝试从/node_modules文件夹中加载第三方模块如果没有找到对应的第三方模块,则移动到再上一层父目录中,进行加载,直到文件系统的根目录
例如,假设在
C:\Users\itheima\project\foo.js文件里调用了require('tools'),则 Node.js 会按以下顺序查找:C:\Users\itheima\project\node_modules\toolsC:\Users\itheima\node_modules\toolsC:\Users\node_modules\toolsC:\node_modules\tools
6.11.5 目录作为模块
当把目录作为模块标识符传递给
require()进行加载时,有三种加载方式在被加载的目录下查找一个叫
package.json的文件,并寻找main属性,作为require()加载的入口如果目录里没有
package.json文件,或者main入口不存在或无法解析,则 Node.js 会试图加载目录下的index.js文件若以上两步都失败了,则 Node.js 会在终端打印错误消息,报告模块的缺失:
Error:Cannot find module 'xxx'


7. express
7.1 简介
7.1.1 是什么
Express是基于 Node.js 平台,快速、开放、极简的 Web 开发框架
简单理解:Express 的作用和 Node.js 内置的 http 模块类似,是专门用来创建 Web 服务器的
Express 的本质:npm 上的第三方包,提供了快速创建 Web 服务器的便捷方法
7.1.2 进一步理解
不使用 Express 能否创建 Web 服务器
- 能,使用原生的 http 模块
有了 http 内置模块,为什么还要用 Express
- http 模块使用较复杂,开发效率低;Express 是基于内置的 http 模块进一步封装出来的,能够提高开发效率
7.1.3 Express 能够做什么
对于前端程序员来说,最常见的两种服务器,分别是
Web 网站服务器:专门对外提供 Web 网页资源的服务器
API 接口服务器:专门对对外提供 API 接口的服务器
使用 Express,可以方便、快速的创建 Web 网站服务器或 API 接口服务器
7.2 基本使用
7.2.1 安装
- 在项目所处的目录中安装 express
npm i express
7.2.2 创建基本的 Web 服务器
// 导入
const express = require("express");
// 创建web服务器
const app = express();
// 调用app.listen(端口号, callback),启动服务器
app.listen(80, () => {
console.log("express server running at http://127.0.0.1");
});
7.2.3 监听 GET 请求
- 通过
app.get()可以监听客户端的GET请求
app.get("请求url", function (req, res) {
/* 处理函数 */
});
req:请求对象,包含了与请求相关的属性和方法res:响应对象,包含了与响应相关的属性和方法
7.2.4 监听 POST 请求
- 通过
app.post()可以监听客户端的POST请求
app.post("请求url", function (req, res) {
/* 处理函数 */
});
7.2.5 把内容响应给客户端
- 通过
res.send()方法,可以把处理好的内容发送给客户端
app.get("/user", function (req, res) {
// 向客户端发送JSON对象
res.send({ name: "zs", age: 18, gender: "男" });
});
app.post("/user", function (req, res) {
// 向客户端发送文本内容
res.send("请求成功!");
});


7.2.6 获取 url 中携带的查询参数
req.query默认是一个空对象客户端使用
?name=zs&age=18这种查询字符串形式发送到服务器,可以通过req.query对象访问到,如:req.query.name和req.query.age
app.get("/", function (req, res) {
console.log(req.query);
res.send(req.query);
});

7.2.7 获取 url 中的动态参数
通过
req.params对象,可以访问到 url 中通过:匹配到的动态参数req.params默认是一个空对象动态参数可以有多个,如:
/user/:id/:name
// 此处:id是一个动态参数
app.get("/user/:id", function (req, res) {
console.log(req.params);
res.send(req.params);
});

7.3 托管静态资源
7.3.1 express.static()
通过
express.static()可以非常方便地创建一个静态资源服务器示例:将 clock 目录下的文件对外开放访问
app.use(express.static("./clock"));
此时,可以访问
clock目录下的所有文件了http://127.0.0.1/index.html
注:Express 在指定的静态目录中查找文件,并对外提供资源的访问路径,存放静态文件的目录名不会出现在 url 中

- 如果要托管多个静态资源目录,需要多次调用
express.static()
app.use(express.static("./clock"));
app.use(express.static("./files"));
注:访问静态资源文件时,
express.static()会根据目录的添加顺序查找所需的文件,即如果两个文件夹中存在同名文件,以前面的为主把
./files放前面,访问到的就是files中的index.html文件

7.3.2 挂载路径前缀
- 如果希望在托管的静态资源访问路径之前挂载路径前缀,可使用如下方式
app.use("/clock", express.static("./clock"));
- 注:此后访问资源时都必须加上前缀

7.4 nodemon
在编写调试 Node.js 项目时,如果修改了项目的代码,需要频繁手动关闭再重启,比较繁琐
此时,可以使用
nodemon工具,它可以监听项目文件的变动,当代码被修改时,nodemon会自动重启项目,方便开发和调试安装
npm i -g nodemon
- 用
nodemon app.js代替传统的node app.js启动项目

7.5 路由
7.5.1 概念
在 Express 中,路由指的是客户端的请求与服务器处理函数之间的映射关系
Express 中的路由分为 3 部分组成,分别是请求的类型、请求的 url 地址、处理函数
app.method(path, handler);
- 前面使用过的
app.get()、app.post()便是路由
7.5.2 路由的匹配过程
每当一个请求到达服务器之后,需要先经过路由的匹配,只有匹配成功之后才会调用对应的处理函数
匹配时,会按照路由的顺序进行匹配,如果请求类型和请求的 url 同时匹配成功,则 Express 会将这次请求转交给对应的 function 函数进行处理
路由匹配注意点
按照定义的先后顺序进行匹配
请求类型和请求的 url 同时匹配成功,才会调用对应的处理函数
7.5.3 使用
为了方便对路由进行模块化的管理,Express 不建议将路由直接挂载到 app 上,而是推荐将路由抽离为单独的模块
步骤
创建路由模块对应的
.js文件调用
express.Router()函数创建路由对象向路由对象上挂载具体的路由
使用
module.exports向外共享路由对象使用
app.use()注册路由模块
router.js
// 导入express,创建路由对象
const express = require("express");
const router = express.Router();
// 挂载获取用户列表的路由
router.get("/user/list", function (req, res) {
res.send("Get user list.");
});
// 挂载添加用户的路由
router.post("/user/add", function (req, res) {
res.send("Add new user.");
});
// 向外导出路由对象
module.exports = router;
test.js
const express = require("express");
const router = require("./router");
const app = express();
app.use(router);
app.listen(80, () => {
console.log("express server running at http://127.0.0.1");
});

- 注:
app.use()的作用就是用来注册全局中间件
7.5.4 为路由模块添加前缀
// 导入路由模块
const userRouter = require("./router/user.js");
// 使用app.use()注册路由模块,并添加统一的访问前缀api
app.use("/api", userRouter);
7.6 中间件
7.6.1 概念
中间件:特指业务流程的中间处理环节
生活中的例子
在处理污水时,一般要经过三个处理环节,从而保证处理过后的废水达到排放标准
处理污水的这三个中间处理环节,可以叫做中间件

- 当一个请求到达 Express 的服务器后,可以连续调用多个中间件,从而对这次请求进行预处理

7.6.2 格式
- Express 的中间件,本质上是一个 function 处理函数,格式如下:

注:中间件函数的形参列表中必须包含
next参数,而路由处理函数中只包含req和resnext()是实现多个中间件连续调用的关键,它表示把流转关系转交给下一个中间件或路由
7.6.3 定义中间件
const mw = function (req, res, next) {
console.log("这是一个最简单的中间件函数");
// 在当前中间件的业务处理完毕后,必须调用next()
// 表示把流转关系转交给下一给中间件或路由
next();
};
7.6.4 全局生效的中间件
客户端发起的任何请求到达服务器后,都会触发的中间件,叫做全局生效的中间件
通过调用
app.use(中间件函数),即可定义一个全局生效的中间件
const mw = function (req, res, next) {
console.log("这是一个最简单的中间件函数");
next();
};
// 全局生效的中间件
app.use(mw);
// 简写
app.use(function (req, res, next) {
console.log("这是一个最简单的中间件函数");
next();
});
多个中间件之间共享一份
req和res基于这样的特性,可以在上游的中间件中,统一为
req和res对象添加自定义的属性或方法,供下游的中间件或路由使用

const express = require("express");
const app = express();
app.use(function (req, res, next) {
req.name = "张三";
next();
});
app.use(function (req, res, next) {
res.age = 18;
next();
});
app.get("/", (req, res) => {
console.log(req.name, res.age);
res.send("Home page.");
});
app.listen(80, () => {
console.log("express server running at http://127.0.0.1");
});
7.6.5 定义多个全局中间件
- 可以使用
app.use()连续定义多个全局中间件,客户端请求到达服务器之后,会按照中间件定义的顺序依次进行调用
app.use(function (req, res, next) {
console.log("调用了第1个全局中间件");
next();
});
app.use(function (req, res, next) {
console.log("调用了第2个全局中间件");
next();
});
app.get("/", (req, res) => {
res.send("Home page.");
});

7.6.6 局部生效的中间件
- 不使用
app.use()定义的中间件,即局部生效的中间件
const mw = function (req, res, next) {
console.log("这是中间件函数");
next();
};
app.get("/", mw, function (req, res) {
res.send("Home page.");
});
// mw这个中间件不会影响下面这个路由
app.get("/user", function (req, res) {
res.send("User page.");
});
7.6.7 定义多个局部中间件
- 以下两种方式都可以定义多个局部中间件
app.get("/user", mw1, mw2, (req, res) => {
res.send("User page.");
});
app.get("/user", [mw1, mw2], (req, res) => {
res.send("User page.");
});
7.6.8 注意事项
一定要在路由之前注册中间件
客户端发送过来的请求,可以连续调用多个中间件进行处理
执行完中间件的业务代码后,要调用
next()为防止代码逻辑混乱,调用
next()后不要再写额外代码连续调用多个中间件时,多个中间件之间共享
req和res对象
7.6.9 分类
Express 官方把常见的中间件用法分成了 5 大类
应用级别的中间件
路由级别的中间件
错误级别的中间件
Express 内置的中间件
第三方的中间件
应用级别的中间件
- 通过
app.use()、app.get()等绑定到 app 实例上的全局/局部中间件
- 通过
路由级别的中间件
- 绑定到
express.Router()实例上的中间件,其用法与应用级别的中间件没有区别
- 绑定到
const app = express();
const router = express.Router();
// 路由级别的中间件
router.use((req, res, next) => {
console.log("Time:", Date.now());
next();
});
app.use("/", router);
错误级别的中间件
专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题
格式:错误级别中间件的处理函数中含四个参数
function(err, req, res, next)
const express = require("express");
const app = express();
app.get("/", (req, res) => {
throw new Error("出错了!");
res.send("Home page.");
});
app.use((err, req, res, next) => {
res.send(err.message);
});
app.listen(80, () => {
console.log("express server running at http://127.0.0.1");
});
注:错误级别的中间件必须注册在所有路由之后,否则不生效!
Express 内置的中间件(常用的 3 个)
express.static():快速托管静态资源(无兼容性问题)express.json:解析 JSON 格式的请求体数据(4.16.0+ 可用)express.urlencoded:解析 URL-encoded 格式的请求体数据(4.16.0+ 可用)
// 配置解析application/json格式数据的内置中间件
app.use(express.json());
// 配置解析application/x-www-urlencoded格式数据的内置中间件
app.use(express.urlencoded({ extended: false }));
- 示例1:
const express = require("express");
const app = express();
app.use(express.json());
app.get("/user", (req, res) => {
// 没配置express.json()中间件时,默认是undefined
// 配置之后:{ name: 'zhangsan', age: 18 }
console.log(req.body);
res.send("ok");
});
app.listen(80, () => {
console.log("express server running at http://127.0.0.1");
});

- 示例 2:
const express = require("express");
const app = express();
// 解析表单中的url-encoded格式的数据
app.use(express.urlencoded({ extended: false }));
app.post("/book", (req, res) => {
// 在服务器中可以使用req.body来接收客户端发送过来的请求体数据
// 结果:[Object: null prototype] { bookname: '西游记', count: '10' }
console.log(req.body);
res.send("ok");
});
app.listen(80, () => {
console.log("express server running at http://127.0.0.1");
});

第三方的中间件
由第三方开发出来的中间件。在项目中可以按需下载并配置第三方中间件,从而提高开发效率
此处以
body-parser为例,该中间件用来解析请求体数据安装:
npm i body-parser导入:
require('body-parser')注册使用:
app.use()
Express 内置的
express.urlencoded中间件就是基于body-parser进一步封装出来的
const express = require("express");
const app = express();
const parser = require("body-parser");
// 解析表单中的url-encoded格式的数据
app.use(parser({ extended: false }));
app.post("/book", (req, res) => {
// 在服务器中可以使用req.body来接收客户端发送过来的请求体数据
console.log(req.body);
res.send("ok");
});
app.listen(80, () => {
console.log("express server running at http://127.0.0.1");
});
7.6.10 自定义中间件
需求:模拟一个类似于
express.urlencoded的中间件来解析 post 提交到服务器的表单数据实现步骤
定义中间件
监听
req的data事件和end事件使用
querystring模块解析请求体数据将解析出来的数据对象挂载为
req.body将自定义中间件封装为模块
说明:
在中间件中,需要监听
req对象的data事件来获取客户端发送到服务器的数据如果数据量较大,无法一次性发送完毕,则客户端会把数据切割后分批发送到服务器,所以
data事件可能会触发多次,每次触发data事件时,获取到数据只是完整数据的一部分,需要手动对接收到的数据进行拼接当请求体数据接收完毕后,会自动触发
req的end事件因此,可以在
req的end事件中拿到并处理完整的请求体数据Node.js 内置了
querystring模块,专门用来处理查询字符串,通过该模块的parse()可以将查询字符串解析成对象的格式将解析出来的数据挂载为
req的自定义属性,命名为req.body,供下游使用最后将自定义的中间件封装为独立的模块
custom-body-parser/index.js
// 导入querystring模块解析请求体数据
const qs = require("querystring");
const parser = (req, res, next) => {
// 存储客户端发送过来的请求体数据
let str = "";
req.on("data", chunk => {
// 拼接请求体数据
str += chunk;
});
req.on("end", () => {
// 打印完整的请求体数据
console.log(str);
// 调用qs.parse()把查询字符串解析为对象,并挂载为req.body
req.body = qs.parse(str);
next();
});
};
module.exports = parser;
test.js
const express = require("express");
const app = express();
const parser = require("./custom-body-parser");
app.use(parser);
app.post("/book", (