Node.js

定义

nodejs是前端工程化的基础,是开源的,基于谷歌v8引擎构建的js运行环境,运行开发者使用js编写的服务器。

三大模块

文件相关

  • fs模块

    封装了与本机文件系统进行交互的方法与属性

    fs.readFile()

    1
    fs.readFile(path[, options], callback)
    • 参数1:必选参数,字符串,表示文件路径

    • 参数2:可选参数,表示以什么编码格式来读取文件。

    • 参数3:必选参数,文件读取完成后,通过回调函数拿到读取的结果,形如(err,dataStr)=>{}

      读取成功err为null,否则为错误对象

    示例:

    1
    2
    3
    4
    fs.readFile('/path/to/file.txt', 'utf8', (err, data) => {
    if (err) throw err;
    console.log(data);//data是文件内容的buffer数据流
    });

    fs.writeFile()

    1
    fs.writeFile(file, data[, options], callback)
    • 参数1:必选参数,需要指定一个文件路径的字符串,表示文件的存放路径
    • 参数2:必选参数,表示要写入的内容
    • 参数3:可选参数,表示以什么格式写入文件内容,默认值是utf-8
    • 参数4:必选参数,文件写入完成后的回调函数

    注意:这个方法只能用来创建文件,不能用来创建路径

  • path模块

    path 模块提供了用于处理文件路径的方法,帮助我们在不同操作系统之间处理和标准化路径字符串

    • __dirname:返回当前js文件所在目录的绝对路径

    • path.join()

      path.join() 方法则简单地将所有给定的路径片段连接在一起,并规范化生成的路径。它不会尝试将路径转换为绝对路径,也不会考虑当前工作目录。

      1
      2
      3
      const path = require('path');
      const filePath = path.join('/folder', 'subfolder', 'file.txt');
      console.log(filePath); // 输出:'/folder/subfolder/file.txt'
    • path.resolve()

      path.resolve() 方法会将传入的路径片段解析为绝对路径。它从右向左处理参数,直到构造出一个绝对路径为止。如果所有给定的路径片段都不是绝对路径,则会使用当前工作目录(process.cwd())作为基础来构建绝对路径。无论输入是什么,path.resolve() 总是返回一个绝对路径。

    • path.parse()

      被用来解析路径

      1
      2
      3
      const pathObj = path.parse('/folder/subfolder/file.txt');
      // 输出:{ root: '/', dir: '/folder/subfolder', base: 'file.txt', ext: '.txt', name: 'file' }
      console.log(pathObj);

网络相关

http模块

对标浏览器中的XMLHttptRequest模块,http 模块用于创建 HTTP 服务器或客户端

1
2
3
4
5
6
7
8
9
10
const http = require('http');
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello World\n');
});
//开启服务,占用3000端口
server.listen(3000, () => {
console.log('Server running on port 3000');
});
  • req

    req是请求对象,它包含了与客户端相关的数据和属性,

    • req.url:是客户端请求的 URL地址
    • req.method:是客户端的请求方法
  • res

    res是响应的结果,或者说是响应报文

    • res.statusCode:设置响应状态码

    • res.setHeader():设置响应报文的响应头

      1
      res.setHeader('Content-Type', 'text/plain;charset=utf-8')
    • res.end():结束此次请求与响应,并返回数据,当调用res.end()方法,并向客户端发送中文内容的时候,会出现乱码问题,此时,需要手动设置内容的编码格式。即res.setHeader('Content-Type', 'text/plain;charset=utf-8')

客户端/服务器

在网络节点中,负责消费资源的电脑,叫做客户端;负责对外提供网络资源的电脑,叫做服务器。

总结

这三个模块的组合使用非常常见,尤其是在构建需要读写文件并提供网络服务的应用程序中。例如,你可能需要从文件系统读取配置文件,然后使用这些配置来启动一个 HTTP 服务器,或者接收 HTTP 请求并将请求的数据写入文件系统。

Nodejs与浏览器的区别与联系

  • 浏览器依靠内核中的V8引擎执行js代码,node.js基于谷歌V8引擎进行封装
  • 都支持ECMAscript基础语法(ES语法)
  • Node.js有独立的api,没有DOM,BOM

模块化

模块化发展流程

  • 把功能封装为一个一个函数,把函数和相关的变量放到一个js文件中,需要使用某个函数的时候就引入js文件。引入的函数会被挂载到全局对象上,存在全局变量污染的问题。

    1
    2
    3
    4
    5
    6
    7
    8
    // math.js
    function add(a, b) {
    return a + b;
    }
    function subtract(a, b) {
    return a - b;
    }
    // 在HTML文件中通过script标签引入math.js后,add和subtract会挂载到全局对象上
  • 所以就把相关的函数变量封装到一个对象中,这样即便存在同名的函数也没关系,但是这样不安全,因为对象的属性可以被随意修改

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // math.js
    var MathLib = {
    add: function(a, b) {
    return a + b;
    },
    subtract: function(a, b) {
    return a - b;
    }
    };
    // 在HTML文件中引入math.js后,MathLib.add 和 MathLib.subtract不会直接挂载到全局对象上
  • 使用立即执行函数私有化变量函数,并使用return暴露函数。这种写法的,被引入的函数和相关变量不再挂载到全局对象,必须要求原模块代码先于拓展模块代码被引入。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // math.js
    var MathLib = (function() {
    // 私有变量和函数
    var privateVar = 'secret';

    function privateFn() {
    console.log('This is private:',privateVar);
    }

    // 返回公开接口
    return {
    privateFn
    };
    })();
  • 为了解决先后顺序问题,再传参的时候判断一下如果传入的是undefined则转为传入{}

  • 可以看出模块化实现有多种方式,为了统一,我们必须制定出一个好用统一模块化标准,比如commonjsesm,他们被打包后都会转换成立即执行函数风格的模块化代码。

模块分类

  • 内置模块

    加载的时候直接写包名,比如上述介绍的三大模块

  • 第三方模块

    加载的时候直接写包名,第三方模块又叫做包,是基于内置模块封装出来的

  • 自定义模块

    加载的时候需要写路径,可以省略.js后缀名,后缀补全规则

    1
    2
    3
    4
    5
    按照确切的文件名进行加载
    补全.js扩展名进行加载
    补全.json扩展名进行加载
    补全.node扩展名进行加载
    加载失败,终端报错

模块作用域

模块内定义的变量和函数无法被外部访问

模块加载机制

模块在第一次加载后会被缓存。这也意味着多次调用require()不会导致模块内的代码被执行多次

  • 内置模块

    加载优先级最高

  • 自定义模块

    加载时必须指定以./../开头的路径标识符,否则则node会把它当作内置模块第三方模块进行加载。

  • 第三方模块

    如果传递给require()模块标识符不是一个内置模块,也没有以./../开头,则Node.js 会从当前模块的父目录开始,尝试从/node_modules文件夹中加载第三方模块。如果没有找到对应的第三方模块,则再移动到上一层父目录中,进行加载,直到文件系统的根目录

  • 目录作为模块(没有指明文件名)

    在被加载的目录下,查找package.json的文件,并寻找 main属性,作为require()加载的入口
    如果目录里没有package.json文件,或者main入口不存在或无法解析,则Node.js将会式图加载该目录下的index.js文件。
    如果以上两步都失败了,则Node.js 会在终端打印错误消息,报告模块的缺失: Error: Cannotfind module ‘xxx’

两种模块化标准

CommonJS

nodejs默认支持的模块化语法。

导入

使用 require 函数来导入模块,导入自定义模块写相对路径,导入第三方模块或者内置模块使用模块名

1
const myModule = require('./myModule');

或者

1
const http = require('http');

通过require导入模块的方式都是同步的,也就是说会阻塞后续的代码的执行。

导出

通常使用module.exports,只能导出一个对象。

1
2
3
4
5
module.exports = {
sayHello: function(name) {
return `Hello, ${name}`;
}
};

module对象

  • 在每个.js自定义模块中都有一个module对象,它里面存储了和当前模块有关的信息

  • 在自定义模块中,可以使用module.exports对象,将模块内的成员共享出去,默认为{}

  • module.exports可以直接写成exports,它们起初指向同一个空对象

    1
    2
    3
    4
    5
    6
    function add(x,y){
    return x+y
    }
    //module.exports.add = add 如果使用这种导出方式,module.exports和exports指向的对象都包含add方法
    //module.exports = {add} 如果使用这种导出方式,module.exports指向的对象都包含add方法,但是exports指向的还是空对象
    //exports = {add} 如果使用这种导出方式,module.exports指向的对象还是空对象,exports指向的是包含add方法的对象
  • require()模块时,得到的永远是module.exports指向的对象

  • 在js文件中使用了module,就可以认为这个js文件是commonjs模块

ESM

ES模块功能主要由两个命令构成:

  • export:用于规定模块的对外接口
  • import:用于输入其他模块提供的功能

import

在编译阶段,import提升到整个模块的头部,首先执行

1
2
3
foo();

import { foo } from 'my_module';

多次重复执行同样的导入,只会执行一次

1
2
import 'lodash';
import 'lodash';

export

一个模块就是一个独立的文件,该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。简单的来说,export就是用来暴露模块内部私有的变量的。

命名导出/导入

  • 命名导出

    1
    2
    export const PI = 3.14159;
    export const add = function(a,b){return a+b}//math.js

    或者写成

    1
    2
    3
    const PI = 3.14159;
    const add = function(a,b){return a+b}
    export {PI,add}

    但是不能写成

    1
    2
    3
    4
    const PI = 3.14159;
    const add = function(a,b){return a+b}
    export PI //报错
    export add //报错
  • 命名导入

    1
    2
    3
    4
    //import后面接着from关键字,from指定模块文件的位置,可以是相对路径,也可以是绝对路径
    import { PI, add } from './math.js';
    console.log(PI); // 输出: 3.14159
    console.log(add(1, 2)); // 输出: 3

    如果想要给输入变量起别名,通过as关键字

    1
    import { lastName as surname } from './profile.js';

    输入的变量都是只读的,不允许修改,但是如果是对象,允许修改属性

    1
    2
    3
    4
    import {a} from './xxx.js'

    a.foo = 'hello'; // 合法操作
    a = {}; // Syntax Error : 'a' is read-only;

默认导出/导入

如果不需要知道变量名函数就完成导入,就要用到export default命令,为模块指定默认输出

  • 默认导入

    1
    import obj from '模块名/路径' //obj这个名字是自定义的,可以随便取名
  • 默认导出

    1
    2
    3
    4
    5
    const baseURL = 'http://hmajax.itheima. net '
    const getArraySum = arr => arr.reduce((sum,val) => sum += val,0)
    export default {
    baseURL,getArraysum
    }

    或者直接导出一个函数

    1
    2
    3
    export default function () {
    console.log('foo');
    }

动态加载

允许您仅在需要时动态加载模块,而不必预先加载所有模块,这存在明显的性能优势

这个新功能允许您将import()作为函数调用,将模块的路径作为参数。 它返回一个 promise,可以在then方法中拿到该模块的导出。

1
2
3
4
import('/modules/myModule.mjs')
.then((module) => {
// Do something with the module.
});

根据模块是使用默认导出还是命名导出module 对象的内容会有所不同。

如果模块使用了默认导出(export default),那么动态导入的结果module将是一个带有 default 属性的对象。这个属性的值就是模块中默认导出的内容。例如:

1
2
3
4
// myModule.mjs
export default function() {
console.log('This is a default export.');
}

在这种情况下,动态导入后拿到的 module 对象看起来像这样:

1
2
3
{
default: [Function: anonymous]
}

你可以通过 module.default 来访问默认导出的内容。

如果模块使用了命名导出export),那么动态导入的结果将直接包含这些命名的属性。例如

1
2
3
4
5
// myModule.mjs
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}

在这种情况下,动态导入后的 module 对象看起来像这样:

1
2
3
4
{
PI: 3.14159,
add: [Function: add]
}

可以直接通过 module.PImodule.add 来访问这些命名导出的内容。

如果你的模块同时使用了默认导出命名导出,那么动态导入的结果将会同时包含这两类内容。例如:

1
2
3
4
5
6
7
8
9
10
// myModule.mjs
export const PI = 3.14159;

export function add(a, b) {
return a + b;
}

export default function() {
console.log('This is a default export.');
}

在这种情况下,动态导入后的 module 对象将包括 default 属性以及其他命名导出的属性:

1
2
3
4
5
{
default: [Function: anonymous],
PI: 3.14159,
add: [Function: add]
}

注意

  • 一个模块内可以同时使用命名导出默认导出,但是如果没有默认导出,也不能使用默认导入
  • 不能尝试对默认导入使用对象解构,会被当成按需(命名)导入

切换方法

nodejs默认支持commonjs模块化语法,但是也可以切换为ESM语法,在运行模块所在文件夹新建package.json文件,并设置{ "type" : "module" }这样就能使用ESM语法

定义

模块其他资料聚合成一个文件夹(通常是js文件package.json文件,用来记录包的清单信息),本质就是个文件夹,一个第三方模块。

package.json

package.json 文件是每个 npm 包的核心配置文件,它包含了关于包的元数据信息,如名称、版本、作者等,以及定义了项目的依赖关系、开发依赖关系、脚本命令等。它列出项目的直接依赖项,并指定这些依赖项的版本范围(例如 ^1.2.3~1.2.3)。这意味着安装时(npm i),npm 可以根据这些范围,从注册表中获取满足条件的最新版本。由于它只指定了版本范围而不是具体的版本号,因此允许在一定范围内自动更新依赖项,这可以确保你总是用上最新的修复和改进,但也可能引入不兼容的变化。

package-lock.json

package-lock.json 文件是在 npm 5 引入的一个锁定文件,它记录了所有安装过的依赖及其子依赖的确切版本。不同于 package.json 中的版本范围,package-lock.json 锁定了依赖树中每一个包的具体版本号。这样可以确保无论何时何地重新安装依赖,都能得到完全相同的依赖环境,避免因为不同时间点安装不同版本的依赖而引起的潜在问题,从而提供更为稳定的开发环境

规范的包结构

  • 包必须以单独的目录而存在
  • 包的顶级目录下要必须包含package.json这个包管理配置文件
  • package.json中必须包含nameversionmain这三个属性,分别代表包的名字版本号包的入口

NPM

定义

npm(node packages manager)是node.js的标准软件包管理器,下载好node这个就能用了,通常用来在项目中下载,引入其他已发布的包,类似java中的maven

常用指令

  • npm init -y

    给一个包初始化package.json文件,配置清单信息(记录这个包引入的其他包的信息)

    只能在英文目录下运行,不能包含空格,不能包含中文

  • npm i

    安装所有依赖,包括开发依赖dependencies 和 生产依赖devDependencies

  • npm i 软件包名 -D

    下载软件包到本地开发环境(devDependencies),软件包名和-D的顺序不重要,下方同理

  • npm i 软件包名 -s

    下载安装包到本地的生产环境(dependencies),不添加任何符号,比如npm i 软件包名将默认下载到开发依赖中。

  • npm i 软件包名 -g

    下载软件包到全局。全局安装的软件包不会被添加到任何package.json文件中,因为它们不属于特定的项目,全局安装的软件包会被放置在系统的全局 Node.js 安装目录下(C:\Users\35194\AppData\Roaming\npm\node_modules)

  • npm i 包1 包2….

    一次性安装多个包(用空格隔开)

  • npm uninstall 包名

    卸载包,package.json文件中内容也会改变

推荐的包

nrm

一个方便切换下载源的工具包,选择合适的下载源,能显著提高我们下载包的速度。

安装指令:

1
npm i -g nrm

不借助nrm切换下载源:

1
2
npm config get registry //获取下包的服务器地址
npm config set registry = https://registry.npm.taobao.org //修改包的下载地址

明显比较麻烦,需要记住指令下载源的网址

使用nrm

1
2
nrm ls //获取所有可用镜像源
nrm use 镜像源名 //切换下载源

i5ting_toc

一个可以把md文档转换成html的包

安装:

1
npm install -g i5ting toc

使用:

1
i5ting_toc -f 要转换的md文件路径 -o

nodemon

安装:

1
npm install -g nodemon //安装到全局

或者

1
npm install --save-dev nodemon //安装到本地开发环境

安装完成后,你可以直接用 nodemon 来代替 node 命令来启动你的应用程序。例如,如果你的应用入口文件是 app.js,你可以这样做:

1
nodemon app.js

当你修改app.js文件并保存时,nodemon会检测到这个文件变化,并自动重新运行这个文件。

mysql

mysql模块是托管于npm 上的第三方模块。它提供了在 Node.js项目中连接和操作 MySQL数据库的能力。

express

是什么

express 是一个极简且灵活的基于Node.js 的Web 应用框架,它为构建 Web 应用和 API 提供了一组强大的功能。Express 通过提供路由、中间件、模板引擎等功能简化了 HTTP 服务器的创建过程,并支持快速开发可扩展的应用程序。

安装

1
npm i express

导入

1
2
3
4
// 引入 express 模块
const express = require('express');
// 创建 express 应用实例
const app = express();

根据语法写业务函数

1
2
3
4
5
6
app.get("/api/complex",(req,res)=>{
// 一个字母都不能写错
// 解决复杂跨域问题
// res.setHeader("Access-Control-Allow-Origin","http://127.0.0.1:5500")
res.send(data)//不需要转化成json
})
  • req

    • req.query:获取url中的查询参数,默认是个空对象

    • req.body:获取请求的请求体

    • req.params:

      1
      2
      3
      4
      5
      app.get( '/user/:id', (req,res) =>{ 
      //req.params 默认是一个空对象
      //里面存放着通过︰动态匹配到的参数值
      console.log(req.params)
      })

      可以传入多个动态参数,比如/user/:id/:name

  • res

    • res.send(‘数据’):在响应体中携带数据,并返回响应

    • res.sendFile(‘’文件路径’’)

      res.sendFile 方法自动处理文件的读取发送,减少了手动读取文件设置响应头的工作,它会根据文件扩展名自动设置响应头中的 Content-Type 字段。

开启服务

1
2
3
app.listen(8081,()=>{
console.log("服务器启动")
})//服务器对应的域名是http://127.0.0.1:8081

然后执行,node express.js启动服务器(上述代码都写在express.js文件中),每次修改服务器内容后都需要重新启动,所以建议使用nodemon express.js

配置静态资源

1
app.use(express.static('./public'))

上述代码指定了,服务器的静态资源根目录,为与express.js文件同一级别的public文件

1
2
3
4
5
6
7
8
9
/your-project
/public
/images
logo.png
/stylesheets
style.css
/javascripts
script.js
app.js

这意味着如果客户端请求 http://127.0.0.1:8081/images/logo.pngExpress 会尝试从 public/images/logo.png 提供该文件。

指定虚拟路径前缀

果你想为静态资源设置一个虚拟路径前缀(例如 /static),可以这样做:

1
app.use('/static', express.static('public'));

这行代码意味着所有的静态资源都将通过 /static 开头的 URL 路径访问。比如,要获取 public/images/logo.png 文件,客户端应该请求 http://127.0.0.1:8081/static/images/logo.png

配置多个静态资源

如果你有多个静态资源目录,可以多次调用 express.static

1
2
app.use(express.static('public'));
app.use(express.static('assets'));

这样,Express 会按照定义的顺序查找静态文件。首先会在 public 目录下查找,如果没有找到,再尝试在 assets 目录中查找。

路由

在Express 中,路由指的是客户端的请求服务器处理函数之间的映射关系,也可以说是后端路由。和匹配静态资源类似,在匹配时,会按照路由的声明/书写顺序进行匹配。

路由模块化

  • 创建路由文件

    首先,在你的项目中创建一个 routes 目录,并为每个资源创建单独的路由文件。例如,如果你有一个用户资源和一个产品资源,可以创建如下结构:

    1
    2
    3
    4
    5
    /your-project
    /routes
    user.js
    product.js
    app.js
  • 定义路由

    在每个路由文件中,使用 express.Router() 来创建一个新的路由器对象,并定义与该资源相关的路由。然后导出这个路由器对象。以用户资源为例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // routes/user.js
    const express = require('express');
    const router = express.Router();

    // 定义用户的 GET 路由
    router.get('/info', (req, res) => {
    res.send('GET request to the user route');
    });

    // 定义用户的 POST 路由
    router.post('/info', (req, res) => {
    res.send('POST request to the user route');
    });

    // 导出路由器
    module.exports = router;
  • 加载路由模块

    接下来,在主应用文件(通常是 app.js)中引入这些路由模块,并使用 app.use() 方法挂载它们。你还可以为每个路由模块指定一个基础路径

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // app.js
    const express = require('express');
    const app = express();
    const userRouter = require('./routes/user');
    const productRouter = require('./routes/product');

    // 挂载用户路由,基础路径为 /users
    // 这样就能通过 /users/info访问到users对应的资源
    app.use('/users', userRouter);

    // 挂载产品路由,基础路径为 /products
    app.use('/products', productRouter);

    // 启动服务器
    const PORT = process.env.PORT || 3000;
    app.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
    });

中间件

定义

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// routes/user.js
const express = require('express');
const router = express.Router();

// 自定义中间件,用于所有用户路由
router.use((req, res, next) => {
console.log('Time: ', Date.now());
next();
});

// 定义用户的 GET 路由
router.get('/', (req, res) => {
res.send('GET request to the user route');
});

// 导出路由器
module.exports = router;
  • 和路由的区别

    中间件函数的形参列表中,必须包含next 参数。而路由处理函数中只包含reqres

  • next()

    next()是实现多个中间件连续调用的关键,它表示把控制权转交给下一个中间件或路由

  • 共享

    多个中间件之间,共享同一份reqres。基于这样的特性,我们可以在上游的中间件中,统一为 reqres对象添加自定义的属性或方法,供下游的中间件路由进行使用。

  • 执行顺序

    可以使用app.use()连续定义多个全局中间件。客户端请求到达服务器之后,会按照中间件定义的先后顺序依次进行

按作用范围分类

  • 全局中间件

    所有请求到达都会经过的中间件,通过调用app.use(中间件函数),即可定义一个全局生效的中间件

  • 局部中间件

    在定义路由的时候传入中间件

    1
    app.get('/ ', mw1,(req,res) =>{res.send(' Home page. ')})

    定义多个局部中间件

    1
    app.get('/ ', mw1, mw2, (req,res) =>{res.send(' Home page. ')})

    或者在一个单独的模块中定义,通过router.use(中间件函数)实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // routes/user.js
    const express = require('express');
    const router = express.Router();

    // 自定义中间件,用于所有用户路由
    router.use((req, res, next) => {
    console.log('Time: ', Date.now());
    next();
    });

其他分类

  • 应用级别:绑定到app上的

  • 路由级别:绑定路由上的

  • 误差级别中间件:专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题。区别于其他中间件,必须放在所有路由之后,可以观察到,错误级别的中间件有四个参数,(err, req, res, next)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    app.use((err,req,res,next)=>{
    if(err.inner.message = "No authorization token was found"){
    res.send({
    code:1,
    msg:"token未携带"
    })
    }
    next('token未携带')//它最终会被传递到最后一个中间件,通常是日志记录中间件,然后打印到控制台
    })
  • 内置中间件

    • express.static()

      快速托管静态资源的内置中间件,所以说,express.static()返回一个新的中间件函数

      1
      app.use(express.static('public'));
    • express.json()

      这个中间件用于解析JSON格式的请求体。它会将接收到的 JSON 数据解析成 JavaScript 对象,并将其存储在 req.body 中,以便后续的路由处理器可以访问。

    • express.urlencoded()

      此中间件用于解析 URL 编码格式的请求体,通常出现在 HTML 表单提交时。与 express.json() 类似,它也会将解析后的数据附加到 req.body

  • 自定义中间件

    定义一个模块,编写中间件函数,然后导出,使用的时候导入,再挂载。

    1
    2
    3
    4
    5
    6
    function myMiddleware(req, res, next) {
    // 执行一些操作...

    // 调用 next() 将控制权传递给下一个中间件或路由处理程序
    next();
    }
    1
    2
    3
    4
    5
    const express = require('express');
    const app = express();

    // 定义并使用自定义中间件
    app.use(myMiddleware);
  • 第三方中间件

    下载然后使用,比如cors

注意

  • 一定要在注册路由注册中间价,特别是全局中间价,局部中间件也要先声明在使用。

  • 中间件类别的区分在于参数的个数

  • 如果在中间件中发生错误,应该传递给 next(err),这样可以让错误处理中间件有机会处理该错误

  • 不要忘记调用 next():如果你不调用 next(),请求-响应周期将会停止,导致客户端永远等待响应。