当前位置: 首页 > news >正文

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

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

    • 1. 前言
    • 2. 模块化
      • 2.1 模块化概念
      • 2.2 编程领域中的模块化
      • 2.3 模块化规范
    • 3. Node.js中的模块化
      • 3.1 模块分类
      • 3.2 模块加载
      • 3.3 模块作用域
      • 3.4 向外共享模块作用域中的成员
        • 3.4.1 module对象
        • 3.4.2 module.exports对象
        • 3.4.3 共享成员时的注意点
        • 3.4.4 exports对象
      • 3.5 Node.js 中的模块化规范
      • 3.6 模块的加载机制
        • 3.6.1 优先从缓存中加载
        • 3.6.2 内置模块的加载机制
        • 3.6.3 自定义模块的加载机制
        • 3.6.4 第三方模块的加载机制
      • 4. Express项目组件化
        • 4.1 路由模块 —— router.js
        • 4.2 Express服务模块 —— express_module.js
        • 4.3 服务器入口模块 —— app_server.js
    • 5.总结

面向读者群体

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

技术要求

  • 有HTML、CSS、JavaScript基础更好,当然也没事,就直接运行实例代码学习

专栏介绍

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

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

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')

注意:

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

示例代码:

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

在这里插入图片描述

3.3 模块作用域

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

修改module1.js代码:

// 当前这个文件,就是一个用户自定义模块
console.log('加载了这个用户自定义模块')

// 1.在模块作用域中定义变量
const username = '单片机菜鸟'

// 2.在模块作用域中定义函数
function sayHello(){
    console.log('大家好,我是' + username)
}

在这里插入图片描述

模块作用域的好处:

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

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

3.4.1 module对象

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

// 当前这个文件,就是一个用户自定义模块
console.log('加载了这个用户自定义模块')

// 1.在模块作用域中定义变量
const username = '单片机菜鸟'

// 2.在模块作用域中定义函数
function sayHello(){
    console.log('大家好,我是' + username)
}

// 打印当前module对象
console.log(module)

在这里插入图片描述
特别注意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
}

在这里插入图片描述

调整一下代码:

// 当前这个文件,就是一个用户自定义模块
console.log('加载了这个用户自定义模块')

// 1.在模块作用域中定义变量
const username = '单片机菜鸟'

// 2.在模块作用域中定义函数
function sayHello(){
    console.log('大家好,我是' + username)
}

// 打印当前module对象
console.log(module)

module.exports = {
    username,
    sayHello
}

module.exports = {
    
}

在这里插入图片描述
永远以 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
}

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);
})

4.3 服务器入口模块 —— app_server.js

// 整个app的入口函数

// 1. 导入 express
const express = require('./server/express_module.js')

5.总结

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

相关文章:

  • LeetCode 110.平衡二叉树 (C++)
  • 基于SpringBoot的校园闲置物品交易管理系统
  • 在线表格 循环替换 脚本
  • 量化投资学习——股指期货研究(二)
  • npm下载包速度慢-淘宝NPM镜像服务器--如何切换其他服务器下载
  • 基于elasticjob的入门maven项目搭建
  • 【校招VIP】产品项目分析之竞品分析
  • 服务端(后端)主动通知前端的实现:WebSocket(springboot中使用WebSocket案例)
  • 计算机毕业设计django基于python教学互动系统(源码+系统+mysql数据库+Lw文档)
  • 2022深圳xxx校招Java笔试题目(选择题+简答题)
  • 神经网络训练电脑配置,cpu可以训练神经网络吗
  • RFID读写器的功能
  • 神经元在人体内如何分布,人体神经元怎么分布的
  • Java基础:通过Callable创建多线程
  • 音视频封装格式:MPTG2-TS
  • [iOS]Core Data浅析一 -- 启用Core Data
  • 【css3】浏览器内核及其兼容性
  • Angular 响应式表单之下拉框
  • CEF与代理
  • JAVA SE 6 GC调优笔记
  • macOS 中 shell 创建文件夹及文件并 VS Code 打开
  • mongodb--安装和初步使用教程
  • nginx 负载服务器优化
  • niucms就是以城市为分割单位,在上面 小区/乡村/同城论坛+58+团购
  • php中curl和soap方式请求服务超时问题
  • SpringBoot几种定时任务的实现方式
  • TCP拥塞控制
  • WinRAR存在严重的安全漏洞影响5亿用户
  • 从@property说起(二)当我们写下@property (nonatomic, weak) id obj时,我们究竟写了什么...
  • 服务器从安装到部署全过程(二)
  • 湖南卫视:中国白领因网络偷菜成当代最寂寞的人?
  • 实战:基于Spring Boot快速开发RESTful风格API接口
  • 学习笔记TF060:图像语音结合,看图说话
  • ​总结MySQL 的一些知识点:MySQL 选择数据库​
  • # Panda3d 碰撞检测系统介绍
  • (12)Linux 常见的三种进程状态
  • (Java)【深基9.例1】选举学生会
  • (附源码)计算机毕业设计ssm基于B_S的汽车售后服务管理系统
  • (九)信息融合方式简介
  • (数位dp) 算法竞赛入门到进阶 书本题集
  • (四) 虚拟摄像头vivi体验
  • (四)TensorRT | 基于 GPU 端的 Python 推理
  • (五)关系数据库标准语言SQL
  • (一)python发送HTTP 请求的两种方式(get和post )
  • (转)甲方乙方——赵民谈找工作
  • .bashrc在哪里,alias妙用
  • .NET 8.0 发布到 IIS
  • .NET Core 成都线下面基会拉开序幕
  • .Net CoreRabbitMQ消息存储可靠机制
  • .NET NPOI导出Excel详解
  • .net/c# memcached 获取所有缓存键(keys)
  • .net快速开发框架源码分享
  • .NET下ASPX编程的几个小问题
  • /var/lib/dpkg/lock 锁定问题
  • [ vulhub漏洞复现篇 ] ECShop 2.x / 3.x SQL注入/远程执行代码漏洞 xianzhi-2017-02-82239600