面向读者群体
- ❤️ 电子物联网专业同学,想针对硬件功能构造简单的服务器,不需要学习专业的服务器开发知识 ❤️
- ❤️ 业余爱好物联网开发者,有简单技术基础,想针对硬件功能构造简单的服务器❤️
- ❤️ 本篇创建记录 2023-03-12 ❤️
- ❤️ 本篇更新记录 2023-03-12 ❤️
技术要求
- 有HTML、CSS、JavaScript基础更好,当然也没事,就直接运行实例代码学习
专栏介绍
- 通过简短5天时间的渐进式学习NodeJs,可以了解到基本的服务开发概念,同时可以学习到npm、内置核心API(FS文件系统操作、HTTP服务器、Express框架等等),最终能够完成基本的物联网web开发,而且能够部署到公网访问。
🙏 此博客均由博主单独编写,不存在任何商业团队运营,如发现错误,请留言轰炸哦!及时修正!感谢支持!🎉 欢迎关注 🔎点赞 👍收藏 ⭐️留言📝
前面在讲解很多工程代码的时候,基本上都会把所有代码写在了一个js文件里面。这在编程领域肯定是无法接受的。这就需要我们对项目代码进行分层、拆分模块等等。
用一句话来说就是如何对代码结构进行优化
。
本篇我们会基于express项目讲解如何做合理拆分。
【NodeJs-5天学习】第二天篇③ ——Express Web框架 和 中间件
模块化是指解决一个复杂问题
时,自顶向下逐层把系统划分成若干模块的过程
。对于整个系统来说,模块是可组合、分解和更换的单元。
举个例子:
现代社会人手一个智能手机,从手机上来看,可以分为电路板、液晶屏、手机壳、充电器、电池、耳机等等模块,这些模块可以组合成一台手机,同时如果其中某一个模块坏了也可以直接更换。这就是模块化带来的好处。
编程领域中的模块化,就是遵守固定的规则,把一个大文件拆成独立并互相依赖的多个小模块。
把代码进行模块化拆分的好处:
复用性
可维护性
按需加载
但是需要注意。拆分尽可能围绕单一功能去思考,不要为了拆分而拆分。
模块化规范就是对代码进行模块化的拆分与组合时,需要遵守的那些规则。
引用
模块向外暴露
成员规范:
Node.js 中根据模块来源的不同,将模块分为了 3 大类,分别是:
fs
、path
、http
等express
、body-parser
、moment
等等,这个可以理解为npm
平台所支持的所有模块。任意模块的加载都是通过 require方法,包括 加载需要的内置模块、用户自定义模块、第三方模块进行使用。
// 1.加载内置的 fs 模块
const fs = require('fs')// 2.加载用户的自定义模块
// 注意:在使用 require 加载用户自定义模块期间,
// 可以省略 .js 的后缀名
const custom = require('./custom.js')// 3.加载第三方模块
const moment = require('moment')
注意:
示例代码:
// 注意:在使用 require 加载用户自定义模块期间,
// 可以省略 .js 的后缀名
const m1 = require('./module1.js')
console.log(m1)
// 当前这个文件,就是一个用户自定义模块
console.log('加载了这个用户自定义模块')
和函数作用域类似,在自定义模块中定义的变量、方法等成员,只能在当前模块内被访问,这种模块级别的访问限制,叫做模块作用域
。
修改module1.js代码:
// 当前这个文件,就是一个用户自定义模块
console.log('加载了这个用户自定义模块')// 1.在模块作用域中定义变量
const username = '单片机菜鸟'// 2.在模块作用域中定义函数
function sayHello(){console.log('大家好,我是' + username)
}
模块作用域的好处:
全局变量污染
的问题在每个.js自定义模块中都有一个module
对象,它里面存储了和当前模块有关的信息
,打印如下:
// 当前这个文件,就是一个用户自定义模块
console.log('加载了这个用户自定义模块')// 1.在模块作用域中定义变量
const username = '单片机菜鸟'// 2.在模块作用域中定义函数
function sayHello(){console.log('大家好,我是' + username)
}// 打印当前module对象
console.log(module)
特别注意exports
属性,目前它是空对象。
module.exports
对象,将模块内的成员共享
出去,供外界使用。默认
情况下, module.exports = {}
require()
方法导入自定义模块时,得到的就是 module.exports 所指向的对象
。使用 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最终指向的对象为准。
为了简化向外共享成员的代码,Node 提供了exports对象。默认情况下,exports 和 module.exports 指向同一个对象。最终共享的结果,还是以module.exports指向的对象为准
。
时刻谨记,require()模块时,得到的永远是 module.exports指向的对象。原则上不要同时操作exports和 module.exports两个对象,了解接口。
Node.js遵循了 CommonJS 模块化规范,CommonJS规定了模块的特性和各模块之间如何相互依赖。
CommonJS规定:
模块在第一次加载后会被缓存。 这也意味着多次调用 require()不会导致模块的代码被执行多次
。
注意:不论是内置模块、用户自定义模块、还是第三方模块,它们都会优先从缓存中加载,从而提高模块的加载效率。
内置模块是由 Node.js官方提供的模块,内置模块的加载优先级最高
。
例如:require(‘fs’)始终返回内置的 fs 模块,即使在 node_modules目录下有名字相同的包也叫做fs
使用require()加载自定义模块时,必须指定以./或 ../开头
的路径标识符。在加载自定义模块时,如果没有指定./或…/这样的路径标识符,则 node会把它当作内置模块或第三方模块进行加载。
同时,在使用 require()导入自定义模块时,如果省略了文件的扩展名,则Node.js会按顺序分别尝试加载以下的文件:
如果传递给 require()的模块标识符不是一个内置模块,也没有以 ./ 或 …/开头,则Node.js 会从当前模块的父目录开始,尝试从/node_modules文件夹中加载第三方模块。
如果没有找到对应的第三方模块,则移动到再上一层父目录中,进行加载,直到文件系统的根目录。
例如,假设在C:\Users\260\project\foo.js文件里调用了require(‘tools’),则 Node.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. 导入 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);
})
// 整个app的入口函数// 1. 导入 express
const express = require('./server/express_module.js')
篇④主要是介绍模块化的规则以及注意事项,目的是为了做代码结构优化。最后就以express项目为例进行实验性验证。