物联网服务NodeJs-5天学习第二天篇④ ——项目模块化

Posted 单片机菜鸟哥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了物联网服务NodeJs-5天学习第二天篇④ ——项目模块化相关的知识,希望对你有一定的参考价值。

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

面向读者群体

  • ❤️ 电子物联网专业同学,想针对硬件功能构造简单的服务器,不需要学习专业的服务器开发知识 ❤️
  • ❤️ 业余爱好物联网开发者,有简单技术基础,想针对硬件功能构造简单的服务器❤️
  • ❤️ 本篇创建记录 2023-03-12 ❤️
  • ❤️ 本篇更新记录 2023-03-12 ❤️

技术要求

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

专栏介绍

  • 通过简短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项目为例进行实验性验证。

以上是关于物联网服务NodeJs-5天学习第二天篇④ ——项目模块化的主要内容,如果未能解决你的问题,请参考以下文章

物联网服务NodeJs-5天学习第二天篇② —— 网络编程(TCPHTTPWeb应用服务)

物联网服务NodeJs-5天学习第三天实战篇④ ——QQ机器人,实现自动回复重要提醒

物联网服务NodeJs-5天学习第二天篇③ ——Express Web框架 和 中间件

物联网服务NodeJs-5天学习第四天存储篇④ ——基于MQTT的环境温度检测,升级存储为mysql

物联网服务NodeJs-5天学习第一天篇④ ——了解NodeJs回调函数和事件驱动机制

物联网服务NodeJs-5天学习第三天实战篇② ——基于物联网的WiFi自动打卡考勤系统