CommonJS模块化规范

模块化指自上而下把系统划分成若干模块的过程。

优点:复用性,可维护性,按需加载。

模块的分类

  • 内置模块,由Node.js官方提供;
  • 自定义模块,用户创建的每一个js文件都是自定义模块;
  • 第三方模块,使用前需先下载。

加载模块

​ 使用require()方法加载模块,加载模块时会执行模块中的代码

1
2
3
const fs = require('fs');
// 加载自定义模块需要写出文件路径,".js"可以省略
const index = require('./index.js');

模块作用域

​ 与函数作用域类似,在自定义模块中定义的变量、方法等只能在当前模块使用。

module对象

​ 每个自定义模块中都有一个module对象,里面存储了和当前模块有关的信息,其中exports为暴露的对象,通过exports可以使模块内的成员被外界访问到,导入的结果永远以module.exports指向的对象为准。

1
2
3
4
5
6
7
8
const a = 'a';
function b(){
console.log('b');
}
module.exports = {
a,
b
}

exports对象

​ 默认情况下,exportsmodule.exports指向同一个对象,最终结果依旧以module.exports为准。

​ 但是,不能直接给exports赋值一个对象,需要对他的属性赋值。

1
2
export.a = 'a'; // 正确
export = {a: 'a'}; // error

注意

​ 默认情况下,exportsmodule.exports指向同一个对象,当直接添加属性时,会在原有的内存里添加属性,但如果对module.exports重新赋值一个对象,就会在内存中开辟一个新的空间存放赋值的属性与方法,原来对其属性的赋值都无效,也不再与exports指向一个对象。

​ 为防止混乱,不要在同一个模块中同时使用exportsmodule.exports

​ 一个JS模块在node环境中执行时,是被包裹在一个内置函数中执行的,格式如下

1
function (exports, require, module, __filename, __dirname){}

​ node.js默认支持CommonJS,但浏览器不支持,因此要在浏览器端运行需要经过编译。

xxxxxxxxxx const express = require(‘express’);const app = express();app.get(‘/‘, (req, res) => {    // 从客户端得到函数的名称    const fnname = req.query.fnname;    // 定义要发送到客户端的数据对象    const data = {        name: ‘jsonp’,        age: 18   }    // 拼接出一个函数的调用    const fn = ${fnname}(${JSON.stringify(data)});    res.send(fn);})js

  • const express = require('express');
    const app = express();app.get('/', (req, res) => {    // 从客户端得到函数的名称    
        const fnname = req.query.fnname;    // 定义要发送到客户端的数据对象    
        const data = {        
            name: 'jsonp',        
            age: 18    
        }    
        // 拼接出一个函数的调用    
        const fn = `${fnname}(${JSON.stringify(data)})`;    
        res.send(fn);
    }
    
    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
    - `package.json`用来记录每个包的下载信息,名字、版本号、下载地址等。

    - `npm init -y`快速创建`package.json`文件。

    - **dependencies节点:**用来记录使用安装了哪些包(**核心依赖包**)。

    - 使用`npm i`命令一次性安装所有dependencies中记录的包。
    - **devDependencies节点:**记录只在项目开放阶段使用,上线后不用的包(**开发依赖包**),安装时在命令中加上`-D`(等价于`--save-dev`)即可。

    - 在执行`npm install`命令时,若添加了`-g`参数,则会安装为全局包。

    ### 模块的加载机制

    - 模块在第一次加载后会被缓存,多次调用require()不会导致模块中的代码执行多次。
    - **内置模块**加载优先级最高。
    - 加载自定义模块路径必须为相对路径,否则会被当做内置模块或第三方模块加载。

    ## Express

    ​ **Express**是基于Node.js平台,快速、开放、极简的**Web开发平台**,与http模块类似。

    ​ 对前端来说,最常见的两种服务器分别是**Web网站服务器**、**API接口服务器**。

    ```js
    const express = require('express');
    // 创建服务器
    const app = express();
    app.get('/',(req, res) => {
    // 把内容响应给客户端
    res.send("666,get成功");
    console.log(req.query);
    })
    app.post('/user', (req, res) => {
    res.send("666,post成功");
    })
    app.listen(8000, ()=>{
    console.log('server started');
    })
  • req.query可以访问客户端通过查询字符串的方式发送的参数,即URL中“?”后的参数,格式为?username=abc&age=18

  • req.params可以访问URL中通过“:”匹配到的动态参数。

  • res.send()将内容响应给客户端,必须存在,否则请求会一直处于待处理状态。

    客户端

    1
    2
    3
    4
    5
    6
    7
    8
    axios.get('http://localhost:8000/paramsname/paramsage',{
    params: {
    name:'queryname'
    }
    }).then((response) => {
    console.log(response.data);
    })
    // 请求成功,输出 666,get成功

    服务端

    1
    2
    3
    4
    5
    6
    7
    app.get('/:name/:age', (req, res) => {
    res.send("666,get成功");
    console.log("paramsName = " + req.params.name);
    console.log("paramsAge = " + req.params.age);
    console.log("queryName = " + req.query.name);
    })
    // 响应成功,输出 paramsName = paramsname paramsAge = paramsage queryName = query

托管静态资源

​ **express.static()**可以创建一个静态资源服务器,通过如下代码即可将public文件夹中的图片、css文件、js文件对外开放访问。

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

​ 如:http://localhost:8000/img/bg.jpg,http://localhost:8000/css/index.css,http://localhost:8000/js/index.js

​ Express在指定的目录中查找文件,并对外提供访问路径,因此存放文件的目录名不会出现在URL中

​ 若要托管多个静态资源目录,可以多次调用express.static()函数,访问文件时会按顺序查找。

挂载路径前缀

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

​ 在访问静态资源时需带有/public前缀

​ 如:http://localhost:8000/public/img/bg.jpg,http://localhost:8000/public/css/index.css

Express 路由

路由指客户端的请求与服务器处理函数之间的映射关系。

模块化路由

​ 为方便对路由的管理,推荐将路由抽离为单独的模块。

1
2
3
4
5
6
7
8
// 路由模块 route1.js
const express = require('express');
const router = express.Router();
router.get('/',(req, res) => {
res.send('get sucess');
})
// 导出路由模块
module.exports = router;
1
2
3
4
5
6
7
8
// 主文件中
const express = require('express');
const app = express();
// 导入路由模块
const route1 = require('./route1.js')
// 注册模块
app.use(route1);
app.listen...

为路由模块添加前缀app.use('/api', route1),添加统一的访问前缀 /api。

Express 中间件

​ 当一个请求到达Express的服务器后,可以连续调用多个中间件对这次请求进行预处理,最终结果通过路由响应给客户端。

​ 中间件本质上是个函数,格式如下:

1
2
3
4
app.get('/', (req, res, next) => {
...
next();
})

​ 中间件必须包含next参数,且必须在最后调用**next()**。

​ next函数表示把流转关系转交给下一个中间件或路由。

​ 客户端发起的任何请求,到达服务器后都会触发的中间叫做全局生效的中间件,格式为app.use((req, res, next) => {})

​ 多个中间件之间共享一份req,res,可以在上游的中间件中添加自定义属性或方法供下游使用。

局部生效的中间件,格式为app.get('/', fn(), (req, res) => {...})app.get('/', fn1(), fn2() (req, res) => {...})app.get('/', [fn1(), fn2()], (req, res) => {...}),其中fn、fn1、fn2为中间件。

中间件分类

  • 应用级别的中间件,绑定到app实例上的中间件。

  • 路由级别的中间件,绑定到express.Router()实例上的中间件。

  • 错误级别的中间件专门用来捕获项目中的错误,形参为(err, req, res),错误级别的中间件必须注册在所有路由之后

    1
    2
    3
    4
    5
    // 错误中间件
    app.use((err, req, res) => {
    console.log(err.message);
    res.send(err.message);
    })
  • Express内置的中间件,包括express.staticexpress.jsonexpress.urlencoded

    1
    2
    3
    4
    // 解析JSON格式的请求体数据
    app.use(express.json());
    // 解析URL/encoded格式的请求体数据
    app.use(express.urlencoded({ extended: false}));

    req.body用来接收客户端发送的请求体数据,若不配置解析表单数据的中间件则默认为undefined

    1
    2
    3
    4
    // 使用axios发送urlencoded格式的数据
    const params = new URLSearchParams('name=abc&age=18');
    params.append('height', 180); // 添加数据
    axios.post('http://localhost:8000', params).then(res => {})
  • 第三方的中间件

自定义中间体

​ 手写一个将JSON数据解析为对象的中间件,类似于express.json。

首先编写实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const express = require('express');
const app = express();
// 解析表单数据的中间件
app.use((req, res, next) => {
let str = '';
// 监听data事件,获取请求体中的数据,只要有数据就会触发
req.on('data',(chunk) => {
str += chunk;
})
// 监听end事件,当数据传输完成时触发
req.on('end',() => {
// 将JSON字符串解析为对象
req.body = JSON.parse(str);
next();
})
})
app.post('/', (req, res) => {
console.log('post success')
res.send(req.body);
})
app.listen(8000, ()=>{
console.log('server started');
})

注意事项

  • next()一定要写在监听end事件函数中,因为监听事件是异步任务,如果将next()写在外面会直接执行next(),直到同步任务执行完毕才会执行监听事件,导致数据未被处理。

封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// jsonparser.js
module.exports = (req, res, next) => {
let str = '';
req.on('data',(chunk) => {
str += chunk;
})
req.on('end',() => {
console.log(str);
req.body = JSON.parse(str);
console.log(req.body);
next();
})
}
-----------------------------------------------
// 使用
const jsonparser = require('jsonparser');
app.use(jsonparser);