物联网服务NodeJs-5天学习第二天篇④ ——项目模块化
Posted 单片机菜鸟哥
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了物联网服务NodeJs-5天学习第二天篇④ ——项目模块化相关的知识,希望对你有一定的参考价值。
【NodeJs-5天学习】第二天篇④ ——项目模块化
面向读者群体
- ❤️ 电子物联网专业同学,想针对硬件功能构造简单的服务器,不需要学习专业的服务器开发知识 ❤️
- ❤️ 业余爱好物联网开发者,有简单技术基础,想针对硬件功能构造简单的服务器❤️
- ❤️ 本篇创建记录 2023-03-12 ❤️
- ❤️ 本篇更新记录 2023-03-12 ❤️
技术要求
- 有HTML、CSS、JavaScript基础更好,当然也没事,就直接运行实例代码学习
专栏介绍
- 通过简短5天时间的渐进式学习NodeJs,可以了解到基本的服务开发概念,同时可以学习到npm、内置核心API(FS文件系统操作、HTTP服务器、Express框架等等),最终能够完成基本的物联网web开发,而且能够部署到公网访问。
🙏 此博客均由博主单独编写,不存在任何商业团队运营,如发现错误,请留言轰炸哦!及时修正!感谢支持!🎉 欢迎关注 🔎点赞 👍收藏 ⭐️留言📝
1. 前言
前面在讲解很多工程代码的时候,基本上都会把所有代码写在了一个js文件里面。这在编程领域肯定是无法接受的。这就需要我们对项目代码进行分层、拆分模块等等。
用一句话来说就是如何对代码结构进行优化
。
本篇我们会基于express项目讲解如何做合理拆分。
2. 模块化
2.1 模块化概念
模块化是指解决一个复杂问题
时,自顶向下逐层把系统划分成若干模块的过程
。对于整个系统来说,模块是可组合、分解和更换的单元。
举个例子:
现代社会人手一个智能手机,从手机上来看,可以分为电路板、液晶屏、手机壳、充电器、电池、耳机等等模块,这些模块可以组合成一台手机,同时如果其中某一个模块坏了也可以直接更换。这就是模块化带来的好处。
2.2 编程领域中的模块化
编程领域中的模块化,就是遵守固定的规则,把一个大文件拆成独立并互相依赖的多个小模块。
把代码进行模块化拆分的好处:
- ① 提高了代码的
复用性
- ② 提高了代码的
可维护性
- ③ 可以实现
按需加载
但是需要注意。拆分尽可能围绕单一功能去思考,不要为了拆分而拆分。
2.3 模块化规范
模块化规范就是对代码进行模块化的拆分与组合时,需要遵守的那些规则。
- 使用什么样的语法格式来
引用
模块 - 在模块中使用什么样的语法格式
向外暴露
成员
规范:
- 大家都遵守同样的模块化规范写代码,降低了沟通的成本,极大方便了各个模块之间的相互调用,利人利己。
3. Node.js中的模块化
3.1 模块分类
Node.js 中根据模块来源的不同,将模块分为了 3 大类,分别是:
- 内置模块
内置模块是由 Node.js 官方提供的,在我们初次安装NodeJs环境时就可以直接使用的,例如fs
、path
、http
等 - 第三方模块
由第三方开发出来的模块,并非官方提供的内置模块,也不是用户创建的自定义模块,使用前需要先下载,例如express
、body-parser
、moment
等等,这个可以理解为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