• 【NodeJs-5天学习】第二天篇④ ——项目模块化


    面向读者群体

    • ❤️ 电子物联网专业同学,想针对硬件功能构造简单的服务器,不需要学习专业的服务器开发知识 ❤️
    • ❤️ 业余爱好物联网开发者,有简单技术基础,想针对硬件功能构造简单的服务器❤️

    技术要求

    • HTMLCSSJavaScript基础更好,当然也没事,就直接运行实例代码学习

    专栏介绍

    • 通过简短5天时间的渐进式学习NodeJs,可以了解到基本的服务开发概念,同时可以学习到npm、内置核心API(FS文件系统操作、HTTP服务器、Express框架等等),最终能够完成基本的web开发,而且能够部署到公网访问。

    学习交流群

    • NodeJs物联网五天入门学习之旅(搜索:729040020

    🙏 此博客均由博主单独编写,不存在任何商业团队运营,如发现错误,请留言轰炸哦!及时修正!感谢支持!🎉 欢迎关注 🔎点赞 👍收藏 ⭐️留言📝

    1. 前言

    前面在讲解很多工程代码的时候,基本上都会把所有代码写在了一个js文件里面。这在编程领域肯定是无法接受的。这就需要我们对项目代码进行分层、拆分模块等等。
    用一句话来说就是如何对代码结构进行优化

    本篇我们会基于express项目讲解如何做合理拆分。

    【NodeJs-5天学习】第二天篇③ ——Express Web框架 和 中间件

    2. 模块化

    2.1 模块化概念

    模块化是指解决一个复杂问题时,自顶向下逐层把系统划分成若干模块的过程。对于整个系统来说,模块是可组合、分解和更换的单元。

    举个例子:

    现代社会人手一个智能手机,从手机上来看,可以分为电路板、液晶屏、手机壳、充电器、电池、耳机等等模块,这些模块可以组合成一台手机,同时如果其中某一个模块坏了也可以直接更换。这就是模块化带来的好处。

    2.2 编程领域中的模块化

    编程领域中的模块化,就是遵守固定的规则,把一个大文件拆成独立并互相依赖的多个小模块。

    把代码进行模块化拆分的好处:

    • ① 提高了代码的复用性
    • ② 提高了代码的可维护性
    • ③ 可以实现按需加载

    但是需要注意。拆分尽可能围绕单一功能去思考,不要为了拆分而拆分。

    2.3 模块化规范

    模块化规范就是对代码进行模块化的拆分与组合时,需要遵守的那些规则。

    • 使用什么样的语法格式来引用模块
    • 在模块中使用什么样的语法格式向外暴露成员

    规范:

    • 大家都遵守同样的模块化规范写代码,降低了沟通的成本,极大方便了各个模块之间的相互调用,利人利己。

    3. Node.js中的模块化

    3.1 模块分类

    Node.js 中根据模块来源的不同,将模块分为了 3 大类,分别是:

    • 内置模块
      内置模块是由 Node.js 官方提供的,在我们初次安装NodeJs环境时就可以直接使用的,例如 fspathhttp
    • 第三方模块
      由第三方开发出来的模块,并非官方提供的内置模块,也不是用户创建的自定义模块,使用前需要先下载,例如 expressbody-parsermoment等等,这个可以理解为npm平台所支持的所有模块。
    • 自定义模块
      用户创建的每个 .js 文件,都是自定义模块。

    3.2 模块加载

    任意模块的加载都是通过 require方法,包括 加载需要的内置模块、用户自定义模块、第三方模块进行使用。

    // 1.加载内置的 fs 模块
    const fs = require('fs')
    
    // 2.加载用户的自定义模块
    // 注意:在使用 require 加载用户自定义模块期间,
    // 可以省略 .js 的后缀名
    const custom = require('./custom.js')
    
    // 3.加载第三方模块 
    const moment = require('moment')
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    注意:

    • 使用 require()方法加载其它模块,会执行被加载模块中的代码
    • 使用 require()方法加载用户自定义模块,可以省略 .js 后缀名

    示例代码:

    • test_module.js
    // 注意:在使用 require 加载用户自定义模块期间,
    // 可以省略 .js 的后缀名
    const m1 = require('./module1.js')
    console.log(m1)
    
    • 1
    • 2
    • 3
    • 4
    • module1.js
    // 当前这个文件,就是一个用户自定义模块
    console.log('加载了这个用户自定义模块')
    
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    3.3 模块作用域

    和函数作用域类似,在自定义模块中定义的变量、方法等成员,只能在当前模块内被访问,这种模块级别的访问限制,叫做模块作用域

    修改module1.js代码:

    // 当前这个文件,就是一个用户自定义模块
    console.log('加载了这个用户自定义模块')
    
    // 1.在模块作用域中定义变量
    const username = '单片机菜鸟'
    
    // 2.在模块作用域中定义函数
    function sayHello(){
        console.log('大家好,我是' + username)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述

    模块作用域的好处:

    • 防止了全局变量污染的问题

    3.4 向外共享模块作用域中的成员

    3.4.1 module对象

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

    // 当前这个文件,就是一个用户自定义模块
    console.log('加载了这个用户自定义模块')
    
    // 1.在模块作用域中定义变量
    const username = '单片机菜鸟'
    
    // 2.在模块作用域中定义函数
    function sayHello(){
        console.log('大家好,我是' + username)
    }
    
    // 打印当前module对象
    console.log(module)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述
    特别注意exports属性,目前它是空对象。

    3.4.2 module.exports对象
    • 在自定义模块中,可以使用module.exports对象,将模块内的成员共享出去,供外界使用。
    • 在一个自定义模块中,默认情况下, module.exports = {}
    • 外界用 require()方法导入自定义模块时,得到的就是 module.exports 所指向的对象
    3.4.3 共享成员时的注意点

    使用 require()方法导入模块时,导入的结果,永远以 module.exports最终指向的对象为准

    案例1:

    // 当前这个文件,就是一个用户自定义模块
    console.log('加载了这个用户自定义模块')
    
    // 1.在模块作用域中定义变量
    const username = '单片机菜鸟'
    
    // 2.在模块作用域中定义函数
    function sayHello(){
        console.log('大家好,我是' + username)
    }
    
    // 打印当前module对象
    console.log(module)
    
    module.exports = {
        username,
        sayHello
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在这里插入图片描述

    调整一下代码:

    // 当前这个文件,就是一个用户自定义模块
    console.log('加载了这个用户自定义模块')
    
    // 1.在模块作用域中定义变量
    const username = '单片机菜鸟'
    
    // 2.在模块作用域中定义函数
    function sayHello(){
        console.log('大家好,我是' + username)
    }
    
    // 打印当前module对象
    console.log(module)
    
    module.exports = {
        username,
        sayHello
    }
    
    module.exports = {
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    在这里插入图片描述
    永远以 module.exports最终指向的对象为准。

    3.4.4 exports对象

    为了简化向外共享成员的代码,Node 提供了exports对象。默认情况下,exports 和 module.exports 指向同一个对象。最终共享的结果,还是以module.exports指向的对象为准

    时刻谨记,require()模块时,得到的永远是 module.exports指向的对象。原则上不要同时操作exports和 module.exports两个对象,了解接口。

    3.5 Node.js 中的模块化规范

    Node.js遵循了 CommonJS 模块化规范,CommonJS规定了模块的特性和各模块之间如何相互依赖。

    CommonJS规定:

    • ① 每个模块内部,module变量代表当前模块。
    • ②module变量是一个对象,它的 exports 属性(即 module.exports)是对外的接口。
    • ③ 加载某个模块,其实是加载该模块的 module.exports 属性。require() 方法用于加载模块。

    3.6 模块的加载机制

    3.6.1 优先从缓存中加载

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

    注意:不论是内置模块、用户自定义模块、还是第三方模块,它们都会优先从缓存中加载,从而提高模块的加载效率。

    3.6.2 内置模块的加载机制

    内置模块是由 Node.js官方提供的模块,内置模块的加载优先级最高

    例如:require(‘fs’)始终返回内置的 fs 模块,即使在 node_modules目录下有名字相同的包也叫做fs

    3.6.3 自定义模块的加载机制

    使用require()加载自定义模块时,必须指定以./或 ../开头的路径标识符。在加载自定义模块时,如果没有指定./或…/这样的路径标识符,则 node会把它当作内置模块或第三方模块进行加载。

    同时,在使用 require()导入自定义模块时,如果省略了文件的扩展名,则Node.js会按顺序分别尝试加载以下的文件:

    • ① 按照确切的文件名进行加载
    • ② 补全 .js 扩展名进行加载
    • ③ 补全 .json 扩展名进行加载
    • ④ 补全 .node 扩展名进行加载
    • ⑤ 加载失败,终端报错
    3.6.4 第三方模块的加载机制

    如果传递给 require()的模块标识符不是一个内置模块,也没有以 ./ 或 …/开头,则Node.js 会从当前模块的父目录开始,尝试从/node_modules文件夹中加载第三方模块。

    如果没有找到对应的第三方模块,则移动到再上一层父目录中,进行加载,直到文件系统的根目录。

    例如,假设在C:\Users\260\project\foo.js文件里调用了require(‘tools’),则 Node.js会按以下顺序查找:

    • ①C:\Users\260\project\node_modules\tools (当前工程目录)
    • ② C:\Users\260\node_modules\tools(上一级目录)
    • ③ C:\Users\node_modules\tools(上一级目录)
    • ④ C:\node_modules\tools(上一级目录)

    在这里插入图片描述

    4. Express项目组件化

    在这里插入图片描述

    4.1 路由模块 —— router.js
    const express = require("express")
    
    // 创建路由对象
    const router = express.Router();
    
    router.get('/api/test1', (req, res) => {
      console.log("请求:GET /api/test1")
      // 获取 URL 中携带的查询参数
      console.log(req.query)
      res.send("/api/test1 get OK")
    })
    
    router.post('/api/test1', (req, res) => {
      console.log("请求:POST /api/test1")
      // 获取 请求体 中携带的内容
      console.log(req.body)
      res.send("/api/test1 Post OK")
    })
    
    router.get('/api/test2', (req, res) => {
      console.log("请求:GET /api/test2")
      // 获取 URL 中携带的查询参数
      console.log(req.query)
      res.send("/api/test2 get OK")
    })
    
    router.post('/api/test2', (req, res) => {
      console.log("请求:POST /api/test2")
      // 获取 请求体 中携带的内容
      console.log(req.body)
      res.send("/api/test2 Post OK")
    })
    
    // all可以匹配任何提交方式 兜底方案
    router.all('*',(req,res)=>{
        // 做一个其它比较友好界面 响应给浏览器
         console.log('页面还没完成,请等待...')
         res.send('页面还没完成,请等待...')
    })
    
    // 4、向外导出路由对象
    module.exports = {
        router
    }
    
    • 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
    4.2 Express服务模块 —— express_module.js
    // 1. 导入 express
    const express = require('express')
    const {getIPAdress} = require('../utils/utils.js')
    const bodyParser = require('body-parser')
    const {router} = require('../router/router.js')
    
    // 2. 创建 web 服务器
    const app = express()
    const port = 8266 // 端口号                 
    const myHost = getIPAdress(); // 获取本机IP地址
    
    // 3.注册中间件,处理业务逻辑
    // 注意:中间件注入顺序,必须严格区分  
    // - 1、预处理中间件(排在最前面)
    // - 2、路由中间件(中间位置,路由分为API路由和静态文件路由)
    // - 3、错误处理中间件(兜底,专门用于捕获整个项目发生的异常错误,防止项目奔溃,必须注册在所有路由之后)
    
    /*********************** 预处理中间件 *************************/
    
    // 解析JSON格式的请求体数据 (post请求:application/json)
    app.use(bodyParser.json());
    // 解析 URL-encoded 格式的请求体数据(表单 application/x-www-form-urlencoded)
    app.use(bodyParser.urlencoded({ extended: true }));
    /*********************** 预处理中间件 *************************/
    
    /*********************** 路由中间件 *************************/
    // 注入API路由中间件
    app.use(router);
    // app.use('/api', router) // 添加/api 访问前缀
    
    // 注入静态路由中间件,快速托管静态资源的中间件,比如 HTML文件、图片、CSS等
    app.use(express.static('web'))
    /*********************** 路由中间件 *************************/
    
    /*********************** 错误处理中间件 *************************/
    app.use((err, req, res, next) => {
      console.error('出现异常:' + err.message)
      res.send('Error: 服务器异常,请耐心等待!')
    })
    /*********************** 错误处理中间件 *************************/
    
    // 4.调用 app.listen(端口号, 启动成功后的回调函数) ,启动服务器
    app.listen(port, () => {
      console.log("express 服务器启动成功 http://"+ myHost +":" + port);
    })
    
    • 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
    4.3 服务器入口模块 —— app_server.js
    // 整个app的入口函数
    
    // 1. 导入 express
    const express = require('./server/express_module.js')
    
    • 1
    • 2
    • 3
    • 4

    5.总结

    篇④主要是介绍模块化的规则以及注意事项,目的是为了做代码结构优化。最后就以express项目为例进行实验性验证。

  • 相关阅读:
    2023年【汽车驾驶员(高级)】考试试卷及汽车驾驶员(高级)理论考试
    2.YOLOv1
    HarmonyOS系统中内核实现NFC无线通信的方法
    CSS的概念和基本用法
    SpringBoot 配置
    《数据仓库入门实践》
    【从零开始学习 SystemVerilog】8.15、SystemVerilog 约束—— Soft Constraints(软约束)
    06.多态
    CSS学习221~249(定位+元素的显示隐藏)
    栈(Stack)和队列(Queue)
  • 原文地址:https://blog.csdn.net/weixin_44614230/article/details/126636257