05_模块化
模块化
学习目标
1、模块化基本概念
1.1、什么是模块化
- 模块化指的是,解决一个 复杂问题的时候,自顶向下逐层将,系统划分成若干模块的过程
- 其实就有点类似于MVC框架的概念,控制层,业务层,数据层,各司其职,这也是模块化的一个缩影
- 总结:模块就是可以组合,分解,更换的单元
1.2、其他例子
- 以前的老式游戏机,组成部分是 游戏机器,和卡带
- 每个卡带对应了一个不同的游戏
- 当你想玩其他游戏的时候,只需要拆下这个卡带,替换成其他游戏卡带即可
- 这就是生活当中模块化的思想
- 而如果你为了玩一个新游戏,单独去买个游戏机,这对金钱,将是绝杀
1.3、编程领域的模块化
- 其实在写业务的时候,我一直都有一个习惯,就是复用代码,提升代码的可读性
- 但是我做的不是很好,项目大了总会将代码写成屎山
- 而代码领域当中的模块化,就是将一个 大文件,拆分成,互相依赖的,独立的小文件
- 需要的时候直接引用即可
- 好处是什么呢?
- 提高代码复读性
- 提高代码可维护性
- 可以实现 按需加载(这个我觉得挺有意思的,因为我之前写项目的时候老是会遇到一个问题)
- 可能这个 按需加载,我会体会到不一样的东西
1.4、模块化 === 规范
模块化规范 就是对代码进行模块化的时候,拆分与组合需要遵守的规则
- 我们需要什么语法来实现代码的模块化?
- 模块化语法后,我们需要怎么向外暴露成员(你模块化的代码如何供其他人调用?)
好处是什么?
2、Node.Js当中的模块化
2.1、分类
在Node当中,根据 “模块来源” 的不同,可以将模块分成三大类
- 内置模块: 例如 http、fs、path,这些就是Node为我们提供的模块,在安装node的时候这些模块被一并安装在了我们的电脑上
- 自定义模块:用户所写的每一个 .js文件,都是自定义模块
- 第三方模块:(第三方开发出来的),非官方提供,使用的时候需要先进行下载
- 区别
- 内置模块和自定义模块都是可以被直接运行的
- 而第三方模块需要先下载才能运行
2.2、加载模块
就是之前一直使用的 require()函数
- 内置模块和第三方模块 都是可以直接写名字进行加载的
- 自定义模块需要提供文件路径位置,可以是相对,也可以是绝对,等等
注意:当使用require()函数的时候,会自动运行里面的代码
测试
查看测试结果
小细节
2.3、模块的作用域
1、作用域概念
- 与函数作用域类似,在一个自定义函数内部所创建的变量是无法被外部访问的
- 同理,在一个模块当中创建的各类变量也是无法被其他模块所访问到的
- 当然,这是正常情况下,需求可不管这些,所以有需求就需要解决需求
- 上述所述,就是模块的作用域概念
2、好处
防止全局变量污染
2.4、向外共享作用域成员
我刚刚就说了,有需求,就需要解决需求,因为这个需求是很常见的;即从一个模块,拿到另一个模块所定义的东西
1、module对象
每个js模块当中都存在一个module对象,这个对象当中存储的是关于这个模块的相关信息,打印结果如下
2、module.exports对象
默认情况下是一个空对象
- 在自定义模块当中,可以使用exports对象,将模块内的成员共享出去,供外界使用
- 外界使用 require()方法,最终会得到一个对象,而这个对象就是 exports对象
- 默认情况下为空
3、共享成员
- 因为exports,本身就是一个空对象
- 那么想共享属性就很简单了,为对象设置一个属性
- 这里我想到了之前有人问过我,为什么在js当中function是一个对象?
- 我在自定义模块文件件当中新建了一个模块,在这个模块当中我为exports对象添加了几个属性
- 在04模块当中,我会引用上述的模块,对exports对象进行输出打印
4、细节
使用require()方法导入模块的时候,导入的结果,永远以 module.exports 所指向的对象为准
- 什么意思呢?
- 首先exports对象是一个空对象
- 当我在为exports对象添加属性的时候,这个对象就不是空对象了
- 但是这个时候我让exports等于另一个对象,那么这时候
- exports的指向就改变了
5、exports对象
- 由于 module.exports对象书写起来比较麻烦,Node当中也提供了exports对象
- exports对象与module.exports指向的是同一个对象
- 也就是说二者的内存地址是相等的
- 最终共享的结果依然是以 module.exports对象为准
6、总结
- 第一个部分是因为
- exports设置了一个属性,当前对象的内容为 {username:'zs'};
- 这个时候exports对象和module.exports指向的都是这个所谓的 ==> {username:'zs'};
- 接下来窒息的一幕来了,module.exports对象指向了一个 新的对象,也就是
- 这个新对象当中当然没有username这个属性,所以username并不会展示出来
- 第二部分,把它俩换个位置,结果是 {username:'zs'}
- 为什么呢?
- 这就要说道之前一直重点提到的概念
- 使用 require()模块得到的对象,永远,永远,永远以module.exports " 所指向的对象为准 "
- 第三部分,这就和第一部分差不多,因为二者指向的都是 所谓的 ==> {username:'zs'};,所以当再添加一个属性的时候,还是往这个对象当中添加
- 第四部分,其实这个也好理解
- exports指向了一个新对象
- 将exports赋值给module.exports,这个时候module.exports与exports的内存地址就相同了
- 接下来再给module.exports对象添加了两个属性
- 本身module.exports对象就不是一个空对象
- 添加属性的时候也是给当前指向的对象添加属性,所以
2.5、CommonJs模块化规范
3、npm与包
3.1、包的概念
- npm当中的模块分为三种,内置模块,自定义模块,和第三方模块
- 其中,第三方模块又叫做包
- 第三方模块和包是一个概念,就像计算机和电脑一样,叫法不同而已
3.2、包的来源
就像接口一样,当你想开发一个业务的时候,如果眼前有现成的东西,且成熟的技术供你使用,那么这种情况下,去使用别人开发好的现成技术会节省很多时间
3.3、为什么需要包
提高开发效率
3.4、下载包
这个网址可以搜索自己需要的包
这个服务器,可以对自己需要的包进行下载
3.5、如何下载包?
npm包管理工具
3.6、小案例,格式化时间
1、自定义模块格式化时间
- 首先,我们需要在这个模块当中创建两个函数
- 补零函数(针对对象是 0-9这个时间分段)
- 格式化时间函数(需要接受一个参数,通过这个参数来格式化时间;当然,我觉得也可以不接收)
- 需要一个Date对象,通过这个对象调用其内部的函数抽取出数字进行补0
- 格式化时间函数调用补0函数,最终将这个function作为属性传递给exports对象
/**
* 1、创建一个补0函数,通过字符串拼接
* @param {需要补0的时间数字} num
*/
function replenish(num){
// 写一个简单的三元表达式
num > 9? num: '0' + num
// 返回给调用者
return num
}
/**
* 2、创建格式化时间的函数
* @param {需要被格式化的时间} time
*/
function dateFormat(date){
// 把这个传递过来的时间格式化一下
// 否则我们无法调用时间戳对象当中的函数
var time = new Date(date)
// 获取年、月、日
var year = time.getFullYear() // 年份不需要补0,获取完整年份
var month = replenish(time.getMonth());
var day = replenish(time.getDay());
// 获取时分秒
var hour = replenish(time.getHours())
var minute = replenish(time.getMinutes())
var second = replenish(time.getSeconds())
// 拼接字符串,使用模板字符
var newDate = `${year}-${month}-${day} ${hour}:${minute}:${second}`;
// 返回这个时间给调用者
return newDate;
}
// 将格式化时间的函数,暴露给外部人员调用
exports.getDate = dateFormat
测试结果
2、使用包格式化时间
我们要使用的这个包是 == moment
- 在我的指定文件夹当中,安装这个包
- 当时用安装命令后(npm i xxx)
- 会创建一个node_moduls的文件夹
- 在与其同级的目录上 会生成两个json文件
- 会创建一个node_moduls的文件夹
- 接下来就是使用这个模块了
- moment模块使用教程
- 详细参数构造
3.7、npm使用注意事项
3.8、安装指定版本的包
如果是同一个包,那么安装其他版本的时候会覆盖当前版本号
3.9、包的语义化版本规范
4、包管理配置文件
4.1、package.json
4.2、团队协作所遇到的问题
- 如上图所述,这个项目的源代码只有 1.6M,但是整个项目有30.4M
- 第三方包就占了绝大部分
- 我们协同开发代码的时候,一般会使用gitee(码云),和github,将如此大量的文件上传到平台上面,无疑会增加很大的工作量
- 况且对于小程序开发而言,如果提供真机展示,整个项目的大小不能超过1MB
- 那么像这种问题如何解决呢?
1、解决思路
- 第三方包都是我们通过联网下载的
- 下载第三方包的时候,会在项目当中创建一个node.modules的文件件用于存放我们的第三方包
- 既然是可以通过联网下载,那么上传的时候只需要不选择这个包或者删除这个包上传即可
- 问题又来了,如何让别人知道我们这个项目当中到底用了哪些包呢?
- 你如果不对第三方包进行标记,他拿到了你的源代码他也很难清楚你需要下载哪些第三方包这个项目才能跑起来
2、包管理配置文件
现在这个文件是自动下载了,可以在这个文件当中查看到我们到底使用了哪些第三方包
3、快速构建package.json文件
这个也不需要做了,现在的node当中已经自动帮我们配置好了,现在需要的是如何通过这个文件,下载我们之前下载的第三方包
4.3、dependencies节点
蜜汁熟悉,学过Maven的对这玩意儿再熟悉不过了,但是我们这里还是要复习下知识点
4.4、一次性安装所有包
这就是团队协作需要知道的点,如何一次性安装,如何根据 "项目包管理文件" 来安装该项目所需要的包
4.5、卸载指定包
只能删除指定的
4.6、devDependencies节点
5、npm模块下载速度慢
5.1、原因
5.2、如何解决
淘宝npm的镜像服务器,和阿里提供的maven镜像是一个含义
5.3、切换npm的下包镜像源
- 首先我们需要 先 查看当前的 == 包镜像资源
- 在CMD命令窗格中,输入 npm config get regitry
- 当前的 包镜像资源是默认的npm镜像资源
- 将默认镜像切换为淘宝镜像
- cmd当中继续输入,npm config set registry=https://registry.npm.taobao.org/
- 没有任何提示,重复步骤1,查看是否切换成功
5.4、nrm工具
- 下载nrm工具
- 输入 npm i nrm -g
- 我在安装的时候出现了问题
- 但是无法使用nrm指令
- 有两种情况
- 资源问题,要配置淘宝镜像(但是我已经配置过了)
- 没有设置环境变量(我是这个问题)
- 解决方案:
- 最终我们输入nrm ls命令,查看可用镜像资源
- 使用淘宝镜像资源,不过这里我使用下腾讯的吧
- nrm use tencent
- 查看是否切换成功
6、包的分类,规范包结构
6.1、概述
- 使用npm包管理工具下载的包,大致可以分为两类
- 项目包
- 开发依赖包:
- 核心依赖包
- 区别 => 安装方式
- 开发依赖包:
- 全局包
- 项目包
6.2、i5ting_toc
可以转换markdown格式为HTML格式;我来试试
- 使用安装指令 npm i i5ting_toc -g
- 测试
- 将指定的md文件转换为html文件
- 建议进入到指定文件夹当中,-f参数后面跟文件名称即可
- 生成完毕后,当前文件夹当中会新创建一个文件夹 == preview
转换效果展示
因为图片用的并不是在线地址,所以说图片显示不出来
6.3、规范的包结构
-
单独存在
-
package.json包含在顶级目录下
-
main属性==>实际上就是,当你使用require函数去导入一个包的时候,通过main属性所指向的对象,去引用。我们在node_modules文件夹下查看我们之前安装的moment模块就知道了
-
包的入口文件
-
-
-
注意
7、案例 == 创建属于自己的包
7.1、需求分析
- 可以格式化时间
- 转译HTML字符(类似于SQL注入)
****
- 还原HTML字符
7.2、初始化包结构配置
- package.json
- README.md文件
- wavesBright.js文件为当前编写自定义包的模块文件
0、细节
- 由上图可知,在拼接文件路径的时候,我并没有将模块(js)文件的路径写上去
- 但是依旧识别出了我在这个模块当中设置的函数和方法
- 这是为什么呢?
- 是由于require函数的底层机制
- 如果当前你给定的是一个文件夹,而非js模块文件
- 那么require的匹配机制就会在这个文件夹(wavesBrightModule)下寻找package.json文件
- 在这个文件夹当中(package.json),他会去根据当前文件配置项的main属性去对模块文件进行查找,有则导入,无则报错
- 也就是说,会去找main所指向的文件模块对象
1、格式化时间功能实现
// 导入moment模块
const moment = require('moment')
// 功能1、格式化时间
function dateFormat(){
// 使用moment模块对得到格式化时间,并返还给用户
var date = moment().format("YYYY-MM-DD hh:mm:ss")
return date;
}
2、对HTML字符进行转译
两种方式
2.1、使用正则表达式
// 功能2、转译html字符;防止HTML注入
function HTMLTranslation(htmlStr){
return String(htmlStr)
.replace(/&(?!\w+;)/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
2.2、视频的方法,这个要简单点
// 老师教的方法
function HTMLTranslation(htmlStr) {
// 首先我们写一个正则表达式
var escape = / <|>|'|"|& /g; // 末尾的g代表进行全局匹配(匹配整个字符串)
// 同样是接受字符串,使用replace函数进行内容替换
return htmlStr.replace(escape, (match) => {
// match代表,replace函数的第一个参数,如果匹配到了,match就是这个匹配结果
switch (match) {
case '<':
return "<";
case ">":
return ">";
case '"':
return """;
case "&":
return "&";
}
})
}
3、还原
// 功能3、还原HTML标签功能
function restoreHtml(escapeHtml){
// 接收一个转译过后的HTML字符串,将其还原为本身内容
// 首先我们写一个正则表达式
var escape = / &|<|>|"|'/g; // 末尾的g代表进行全局匹配(匹配整个字符串)
// 同样是接受字符串,使用replace函数进行内容替换
return escapeHtml.replace(escape, (match) => {
// match代表,replace函数的第一个参数,如果匹配到了,match就是这个匹配结果
switch (match) {
case '&':
return "&";
case "<":
return "<";
case '>':
return ">";
case """:
return '"';
case "'":
return "'";
}
})
}
4、测试
- 将三个函数作为属性传递给exports对象,暴露给外部人员
- 外界成员对模块进行引入,并使用其中 属性 所提供的函数进行测试
- 测试结果如下
// 导入我创建的模块,实现三个功能
const path = require("path")
// 导入包模块对象
const waves = require(path.join(__dirname,'/wavesBrightModule'))
// 1、测试时间功能是否完善
console.log('格式化时间为:' + waves.dateFormat());
console.log("=============================");
// 2、测试HTML内容注入
var str = "<h1 style='color:#ff6700'>这是一个h1盒子<span>这是一个span标签</span></h1>"
// 接受转译过后的转译HTML内容字符串
var escapeStr = waves.HTMLTranslation(str)
console.log("转译内容为:\n" + escapeStr);
console.log("=============================");
// 2、测试HTML内容注入
var escapeStr = waves.restoreHtml(escapeStr)
console.log("还原内容为:\n" + escapeStr);
7.3、将不同的模块进行拆分
注意:我这里的index.js文件是wavesBright.js文件
- 创建src目录
- 新建dateFormat.js文件和htmlEscape.js文件
- 将 原js文件当中的内容进行迁移
- 对原外部的js文件进行内容修改,引入二者的模块
- 重新测试功能查看是否完善
- 没有问题
7.4、编写包说明文档
## 安装方式
```js
// 从npm当中下载该包
npm i wavesbright
```
## 导入方式
```js
const waves = reqiure("wavesBright")
```
## 功能1,格式化时间
```js
var date = waves.dateFormat();
// 格式化时间为:2022-10-02 04:05:54
console.log(date)
```
## 功能2,转译HTML字符内容
```js
// 这是一个测试HTML字符串
var htmlEscape = "<h1 style='color:#ff6700'>这是一个h1盒子<span>这是一个span标签</span></h1>"
// 调用HTMLTranslation对其进行转译
var escapeStr = waves.HTMLTranslation(htmlEscape)
// 打印输出结果
console.log(escapeStr)
// 输出结果为 <h1 style='color:#ff6700'>这是一个h1盒子<span>这是一个span标签</span></h1>
```
## 功能3,还原被转译的HTML字符内容
```js
// 这是一个测试HTML字符串
var htmlEscape = "<h1 style='color:#ff6700'>这是一个h1盒子<span>这是一个span标签</span></h1>"
// 调用HTMLTranslation对其进行转译
var escapeStr = waves.HTMLTranslation(htmlEscape)
// 打印输出结果
console.log(escapeStr)
// 输出结果为 <h1 style='color:#ff6700'>这是一个h1盒子<span>这是一个span标签</span></h1>
```
## 遵守的开源协议为
ISC协议
7.5、发布包到npm
1、注册npm官网账号
- 注册地址
- 注册信息
- 点击注册账号
- 输入邮箱验证信息,略过
2、登录npm账号
注意:登陆之前要将下载包的服务器切换为npm服务器
- 切换完服务器以后,终端当中输入 npm login 指令
- 登录官网
3、把开发完成的包发布到npm上
去官网进行查看
4、测试是否可以下载并使用
使用测试
运行代码
5、删除已发布的包
包名是这个
删除
8、模块的加载机制
8.1、优先从缓存当中加载
- 当我们首次加载 一个模块的时候
- 这个加载的过程会将模块当中的代码运行一遍
- 但是当再次加载的时候就不需要运行了,这样提高了模块的加载效率
8.2、内置模块的加载机制
8.3、自定义模块的加载机制
在使用require()函数导入模块的时候,如果省略了模块的后缀名,则Node.js会按照以下的方式去加载模块
前置工作
- 按照确切的文件名进行加载
- 补全.js后缀进行加载
- 删除text文件
- 补全.json后缀进行加载
- 删除js文件
- 补全.node后缀进行加载
- node文件当中不能写js代码,是基于win32应用程序
- 上述四步都没有加载到,那么就报错
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 后端思维之高并发处理方案
· 理解Rust引用及其生命周期标识(下)
· 从二进制到误差:逐行拆解C语言浮点运算中的4008175468544之谜
· .NET制作智能桌面机器人:结合BotSharp智能体框架开发语音交互
· 软件产品开发中常见的10个问题及处理方法
· 2025成都.NET开发者Connect圆满结束
· 后端思维之高并发处理方案
· 千万级大表的优化技巧
· 在 VS Code 中,一键安装 MCP Server!
· 10年+ .NET Coder 心语 ── 继承的思维:从思维模式到架构设计的深度解析