模块化是指解决一个复杂问题时,自顶向下逐层把系统划分成若干模块的过程。对于整个系统来说,模块是可组合、分解和更换的单元。
编程领域中的模块化,就是遵守固定的规则,把一个大文件拆成独立并互相依赖的多个小模块。
把代码进行模块化拆分的好处:
① 提高了代码的复用性
② 提高了代码的可维护性
③ 可以实现按需加载
模块化规范就是对代码进行模块化的拆分与组合时,需要遵守的那些规则。
例如:
使用什么样的语法格式来引用模块
在模块中使用什么样的语法格式向外暴露成员
模块化规范的好处:大家都遵守同样的模块化规范写代码,降低了沟通的成本,极大方便了各个模块之间的相互调用,利人利己。
Node.js
中的模块化Node.js
中模块的分类Node.js
中根据模块来源的不同,将模块分为了 3 大类,分别是:
内置模块(内置模块是由 Node.js 官方提供的,例如 fs、path、http 等)
自定义模块(用户创建的每个 .js 文件,都是自定义模块)
第三方模块(由第三方开发出来的模块,并非官方提供的内置模块,也不是用户创建的自定义模块,使用前需要先下载)
使用强大的 require()
方法,可以加载需要的内置模块、用户自定义模块、第三方模块进行使用。例如:
// 1.加载内置的 fs 模块
const fs = require('fs')
// 2.加载用户的自定义模块
// 注意:在使用 require 加载用户自定义模块期间,
// 可以省略 .js 的后缀名
const custom = require('./custom.js')
// 3.加载第三方模块
const moment = require('moment')
注意:使用 require()
方法加载其它模块时,会执行被加载模块中的代码
注意:使用 require()
方法加载用户自定义模块期间,可以省略 .js 后缀名
示例代码:
test.js
// 注意:在使用 require 加载用户自定义模块期间,
// 可以省略 .js 的后缀名
const m1 = require('./m1.js')
console.log(m1)
m1.js
// 当前这个文件,就是一个用户自定义模块
console.log('加载了这个用户自定义模块')
和函数作用域类似,在自定义模块中定义的变量、方法等成员,只能在当前模块内被访问,这种模块级别的访问限制,叫做模块作用域
防止了全局变量污染的问题
module
对象在每个.js
自定义模块中都有一个module
对象,它里面存储了和当前模块有关的信息,打印如下:
module.exports
对象module.exports
对象,将模块内的成员共享出去,供外界使用。module.exports = {}
require()
方法导入自定义模块时,得到的就是 module.exports
所指向的对象。使用 require()
方法导入模块时,导入的结果,永远以 module.exports
最终指向的对象为准。
exports
对象由于module.exports
单词写起来比较复杂,为了简化向外共享成员的代码,Node 提供了exports
对象。默认情况下,exports
和 module.exports
指向同一个对象。最终共享的结果,还是以module.exports
指向的对象为准
exports
和module.exports
的使用误区时刻谨记,require()
模块时,得到的永远是 module.exports
指向的对象
exports
和module.exports
的使用注意点Node.js
中的模块化规范Node.js
遵循了 CommonJS
模块化规范,CommonJS
规定了模块的特性和各模块之间如何相互依赖。
CommonJS
规定:
module
变量代表当前模块。module
变量是一个对象,它的 exports
属性(即 module.exports
)是对外的接口。module.exports
属性。require()
方法用于加载模块。npm
与包Node.js
中的第三方模块又叫做包。
就像电脑和计算机指的是相同的东西,第三方模块和包指的是同一个概念,只不过叫法不同。
不同于 Node.js
中的内置模块与自定义模块,包是由第三方个人或团队开发出来的,免费供所有人使用。
注意:Node.js
中的包都是免费且开源的,不需要付费即可免费下载使用
由于 Node.js
的内置模块仅提供了一些底层的 API,导致在基于内置模块进行项目开发的时,效率很低。
包是基于内置模块封装出来的,提供了更高级、更方便的 API,极大的提高了开发效率。
包和内置模块之间的关系,类似于 jQuery 和 浏览器内置 API 之间的关系。
国外有一家 IT 公司,叫做 npm, Inc. 这家公司旗下有一个非常著名的网站: https://www.npmjs.com/ ,它是全球最大的包共享平台,你可以从这个网站上搜索到任何你需要的包,只要你有足够的耐心!
到目前位置,全球约 1100 多万的开发人员,通过这个包共享平台,开发并共享了超过 120 多万个包 供我们使用。
npm, Inc. 公司提供了一个地址为 https://registry.npmjs.org/ 的服务器,来对外共享所有的包,我们可以从这个服务器上下载自己所需要的包。
注意:
npm, Inc. 公司提供了一个包管理工具,我们可以使用这个包管理工具,从 https://registry.npmjs.org/ 服务器把需要的包下载到本地使用。
这个包管理工具的名字叫做 Node Package Manager
(简称 npm 包管理工具),这个包管理工具随着 Node.js 的安装包一起被安装到了用户的电脑上。
大家可以在终端中执行 npm -v
命令,来查看自己电脑上所安装的 npm
包管理工具的版本号:
下载第三方模块需要先修改镜像地址为淘宝的地址
在终端执行如下命令,将镜像修改为淘宝:npm config set registry https://registry.npm.taobao.org
配置后可通过下面方式来验证是否成功:npm config get registry
如果电脑不允许运行脚本,则执行如下操作:
以管理员身份运行终端(powershell)使用:set-ExecutionPolicy RemoteSigned命令将计算机上的执行策略更改为 RemoteSigned,输入Y确定
如果还是不行就输入:get-executionpolicy 获取当前执行权限,然后再执行上面的命令
① 使用 npm
包管理工具,在项目中安装格式化时间的包 moment
② 使用require()
导入格式化时间的包
③ 参考 moment
的官方 API 文档对时间进行格式化
// 1. 导入需要的包
// 注意:导入的名称,就是装包时候的名称
const moment = require('moment')
const dt = moment().format('YYYY-MM-DD HH:mm:ss')
console.log(dt)
npm install 包的完整名称
/npm i 包的完整名称
如果想在项目中安装指定名称的包,需要运行如下的命令:
npm install 包的完整名称
上述的装包命令,可以简写成如下格式:
npm i 完整的包名称
初次装包完成后,在项目文件夹下多一个叫做 node_modules
的文件夹和 package-lock.json
的配置文件。
node_modules
文件夹用来存放所有已安装到项目中的包。require()
导入第三方包时,就是从这个目录中查找并加载包。package-lock.json
配置文件用来记录 node_modules
目录下的每一个包的下载信息,例如包的名字、版本号、下载地址等。注意:不要手动修改node_modules
或 package-lock.json
文件中的任何代码,npm
包管理工具会自动维护它们。
npm i 包的完整名称@版本号
默认情况下,使用 npm install
命令安装包的时候,会自动安装最新版本的包。
如果需要安装指定版本的包,可以在包名之后,通过 @ 符号指定具体的版本,例如:
npm i [email protected]
包的版本号是以“点分十进制”形式进行定义的,总共有三位数字,例如 2.24.0
其中每一位数字所代表的的含义如下:
第1位数字:大版本
第2位数字:功能版本
第3位数字:Bug修复版本
版本号提升的规则:只要前面的版本号增长了,则后面的版本号归零
npm
规定,在项目根目录中,必须提供一个叫做 package.json
的包管理配置文件。用来记录与项目有关的一些配置信息。例如:
项目的名称、版本号、描述等
项目中都用到了哪些包
哪些包只在开发期间会用到
那些包在开发和部署时都需要用到
在项目根目录中,创建一个叫做 package.json
的配置文件,即可用来记录项目中安装了哪些包。从而方便剔除
node_modules
目录之后,在团队成员之间共享项目的源代码。
注意:今后在项目开发中,一定要把node_modules
文件夹,添加到.gitignore
忽略文件中。
npm init -y
npm
包管理工具提供了一个快捷命令,可以在执行命令时所处的目录中,快速创建 package.json
这个包管理配置文件:
// 作用:在执行命令所处的目录中,快速创建package.json 文件
npm init -y
注意:
① 上述命令只能在英文的目录下成功运行!所以,项目文件夹的名称一定要使用英文命名,不要使用中文,不能出现空格。
② 运行 npm install
命令安装包的时候,npm
包管理工具会自动把包的名称和版本号,记录到 package.json
中。
dependencies
节点npm install
/npm i
当我们拿到一个剔除了 node_modules
的项目之后,需要先把所有的包下载到项目中,才能将项目运行起来。
否则会报类似于下面的错误
可以运行npm install
命令(或npm i
)一次性安装所有的依赖包:
npm install
npm uninstall 包名
可以运行 npm uninstall
命令,来卸载指定的包:
npm uninstall 包名
注意:npm uninstall
命令执行成功后,会把卸载的包,自动从 package.json
的 dependencies
中移除掉。
devDependencies
节点如果某些包只在项目开发阶段会用到,在项目上线之后不会用到,则建议把这些包记录到devDependencies
节点中。
与之对应的,如果某些包在开发和项目上线之后都需要用到,则建议把这些包记录到 dependencies
节点中。
您可以使用如下的命令,将包记录到 devDependencies
节点中:
// 安装指定的包,并记录到 devDependencies 节点中
npm i 包名 -D //简写方式
// 注意:上述命令是简写形式,等价于下面完整的写法:
npm install 包名 --save-dev
在使用 npm 下包的时候,默认从国外的 https://registry.npmjs.org/
服务器进行下载,此时,网络数据的传输需要经过漫长的海底光缆,因此下包速度会很慢。
# 查看当前的下包镜像源
npm config get registry
#设置为淘宝镜像
npm config set registry https://registry.npm.taobao.org
#检查是否设置成功
npm config get registry
npm
的下包镜像源下包的镜像源,指的就是下包的服务器地址。
# 查看当前的下包镜像源
npm config get registry
#设置为淘宝镜像
npm config set registry https://registry.npm.taobao.org
#检查镜像源是否设置成功
npm config get registry
为了更方便的切换下包的镜像源,我们可以安装 nrm 这个小工具,利用 nrm 提供的终端命令,可以快速查看和切换下包的镜像源
#安装nrm
npm i nrm -g
#查看可用镜像列表 git 可以i使用
nrm ls
#设置你需要的镜像
nrm use taobao
使用 npm 包管理工具下载的包,共分为两大类,分别是:
项目包
全局包
那些被安装到项目的 node_modules
目录中的包,都是项目包。
项目包又分为两类,分别是:
开发依赖包(被记录到 devDependencies
节点中的包,只在开发期间会用到)
核心依赖包(被记录到 dependencies
节点中的包,在开发期间和项目上线之后都会用到)
npm i 包名 -D #开发依赖包
npm i 包名 #核心依赖包
在执行npm install
命令时,如果提供了 -g
参数,则会把包安装为全局包。
全局包会被安装到 C:\Users\用户目录\AppData\Roaming\npm\node_modules
目录下
npm i 包名 -g #全局安装
npm uninstall 包名 -g # 卸载全局包
注意:
① 只有工具性质的包,才有全局安装的必要性。因为它们提供了好用的终端命令。
② 判断某个包是否需要全局安装后才能使用,可以参考官方提供的使用说明即可
i5ting_toc
i5ting_toc
是一个可以把 md
文档转为html
页面的小工具,使用步骤如下:
# 将 i5ting_toc 安装为全局包
npm install -g i5ting_toc
# 调用 i5ting_toc , 轻松实现md 转html 的功能
i5ting_toc -f 要转化的md文件路径 -o
在清楚了包的概念、以及如何下载和使用包之后,接下来,我们深入了解一下包的内部结构。
一个规范的包,它的组成结构,必须符合以下 3 点要求:
① 包必须以单独的目录而存在
② 包的顶级目录下要必须包含 package.json
这个包管理配置文件
③package.json
中必须包含 name,version,main
这三个属性,分别代表包的名字、版本号、包的入口。
注意:以上 3 点要求是一个规范的包结构必须遵守的格式,关于更多的约束,可以参考如下网址:
https://yarnpkg.com/zh-Hans/docs/package-json
① 格式化日期
② 转义 HTML 中的特殊字符
③ 还原 HTML 中的特殊字符
格式化时间.js
// 1. 导入自己的包
const itheima = require('itheima-tool')
// 格式化时间的功能
const dtStr = itheima.dateFormat(new Date())
console.log(dtStr) // 输出 2021-04-19 18:25:10
console.log('-----------')
// 1. 导入自己的包
const itheima = require('itheima-tool')
// 转义 HTML 中的特殊字符
const htmlStr = '这是h1标签123
'
const str = itheima.htmlEscape(htmlStr)
console.log(str);
// 输出结果:>h1 title="abc"<这是h1标签>span<123 >/span<>/h1<
// 1. 导入自己的包
const itheima = require('itheima-tool')
// 还原 HTML 中的特殊字符
const str2 = itheima.htmlUnEscape(str)
console.log(str2);
// 输出结果: 这是h1标签123
① 新建 itheima-tools
文件夹,作为包的根目录
② 在itheima-tools
文件夹中,新建如下三个文件:
package.json
(包管理配置文件)index.js
(包的入口文件)README.md
(包的说明文档)package.json
{
"name" : "itheima-tools" ,
"version" : "1.0.0" ,
"main" : "index.js" ,
"description" : "提供了格式化时间,HTMLEscape相关的功能" ,
"keywords" : [
"itheima" ,
"dateFormat" ,
"escape"
] ,
"license" : "ISC"
}
关于更多license
许可协议相关的内容,可参考 https://www.jianshu.com/p/86251523e898
index.js
中定义格式化时间的方法// 定义格式化时间的函数
function dateFormat(dateStr) {
const dt = new Date(dateStr)
const y = dt.getFullYear()
const m = padZero(dt.getMonth() + 1)
const d = padZero(dt.getDate())
const hh = padZero(dt.getHours())
const mm = padZero(dt.getMinutes())
const ss = padZero(dt.getSeconds())
return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
}
// 定义一个补零的函数
function padZero(n) {
return n > 9 ? n : '0' + n
}
module.exports = {
dateFormat
}
index.js
中定义转义 HTML
的方法// 定义转义 HTML 字符的函数
function htmlEscape(htmlstr) {
return htmlstr.replace(/<|>|"|&/g, match => {
switch (match) {
case '<':
return '<'
case '>':
return '>'
case '"':
return '"'
case '&':
return '&'
}
})
}
index.js
中定义还原HTML
的方法// 定义还原 HTML 字符串的函数
function htmlUnEscape(str) {
return str.replace(/<|>|"|&/g, match => {
switch (match) {
case '<':
return '<'
case '>':
return '>'
case '"':
return '"'
case '&':
return '&'
}
})
}
① 将格式化时间的功能,拆分到 src -> dateFormat.js 中
② 将处理 HTML 字符串的功能,拆分到 src -> htmlEscape.js 中
③ 在 index.js 中,导入两个模块,得到需要向外共享的方法
④ 在 index.js 中,使用 module.exports 把对应的方法共享出去
包根目录中的 README.md 文件,是包的使用说明文档。通过它,我们可以事先把包的使用说明,以 markdown 的格式写出来,方便用户参考。
我们所创建的这个包的 README.md 文档中,会包含以下 6 项内容:
安装方式
npm install itheima-tools
导入方式
const itheima = require('itheima-tools')
格式化时间
// 调用 dateFormat 对时间进行格式化
const dtStr = itheima.dateFormat(new Date())
// 结果 2020-04-03 17:20:58
console.log(dtStr)
转义 HTML 中的特殊字符
// 带转换的 HTML 字符串
const htmlStr = '这是h1标签123
'
// 调用 htmlEscape 方法进行转换
const str = itheima.htmlEscape(htmlStr)
// 转换的结果 <h1 title="abc">这是h1标签<span>123 </span></h1>
console.log(str)
还原 HTML 中的特殊字符
// 待还原的 HTML 字符串
const str2 = itheima.htmlUnEscape(str)
// 输出的结果 这是h1标签123
console.log(str2)
开源协议:ISC
包的入口文件index.js
// 这是包的入口文件
const date = require('./src/dateFormat')
const escape = require('./src/htmlEscape')
// 向外暴露需要的成员
module.exports = {
...date,
...escape
}
package.json
{
"name": "itheima-tools",
"version": "1.1.0",
"main": "index.js",
"description": "提供了格式化时间、HTMLEscape相关的功能",
"keywords": [
"itheima",
"dateFormat",
"escape"
],
"license": "ISC"
}
src/dateFormat.js
// 定义格式化时间的函数
function dateFormat(dateStr) {
const dt = new Date(dateStr)
const y = dt.getFullYear()
const m = padZero(dt.getMonth() + 1)
const d = padZero(dt.getDate())
const hh = padZero(dt.getHours())
const mm = padZero(dt.getMinutes())
const ss = padZero(dt.getSeconds())
return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
}
// 定义一个补零的函数
function padZero(n) {
return n > 9 ? n : '0' + n
}
module.exports = {
dateFormat
}
src/htmlEscape.js
// 定义转义 HTML 字符的函数
function htmlEscape(htmlstr) {
return htmlstr.replace(/<|>|"|&/g, match => {
switch (match) {
case '<':
return '<'
case '>':
return '>'
case '"':
return '"'
case '&':
return '&'
}
})
}
// 定义还原 HTML 字符串的函数
function htmlUnEscape(str) {
return str.replace(/<|>|"|&/g, match => {
switch (match) {
case '<':
return '<'
case '>':
return '>'
case '"':
return '"'
case '&':
return '&'
}
})
}
module.exports = {
htmlEscape,
htmlUnEscape
}
npm
账号① 访问 https://www.npmjs.com/ 网站,点击 sign up
按钮,进入注册用户界面
② 填写账号相关的信息:Full Name、Public Email、Username、Password
③ 点击 Create an Account
按钮,注册账号
④ 登录邮箱,点击验证链接,进行账号的验证
npm
账号 npm login
第一步:先检查时候在npm上 nrm ls
, 如果不在 nrm use npm
第二步:在要发布的包的根目录打开终端 输入 npm login , 依次输入用户名、密码、邮箱后,即可登录成功。
npm
账号注册完成后,可以在终端中执行 npm login 命令,依次输入用户名、密码、邮箱后,即可登录成功。
注意:在运行npm login
命令之前,必须先把下包的服务器地址切换为npm的官方服务器,否则会导致发布包失败!
npm
上 npm publish
将终端切换到包的根目录之后,运行 npm publish
命令,即可将包发布到 npm 上(注意:包名不能雷同)。
npm unpublish 包名 --force
运行 npm unpublish 包名 --force
命令,即可从 npm
删除已发布的包。
注意:
① npm unpublish
命令只能删除 72 小时以内发布的包
② npm unpublish
删除的包,在 24 小时内不允许重复发布
③ 发布包的时候要慎重,尽量不要往 npm 上发布没有意义的包!
模块在第一次加载后会被缓存。 这也意味着多次调用 require()
不会导致模块的代码被执行多次。
注意:不论是内置模块、用户自定义模块、还是第三方模块,它们都会优先从缓存中加载,从而提高模块的加载效率。
内置模块是由 Node.js
官方提供的模块,内置模块的加载优先级最高。
例如:require('fs')
始终返回内置的 fs 模块,即使在 node_modules
目录下有名字相同的包也叫做fs
使用require()
加载自定义模块时,必须指定以./
或 ../
开头的路径标识符。在加载自定义模块时,如果没有指定./
或../
这样的路径标识符,则 node
会把它当作内置模块或第三方模块进行加载。
同时,在使用 require()
导入自定义模块时,如果省略了文件的扩展名,则Node.js
会按顺序分别尝试加载以下的文件:
① 按照确切的文件名进行加载
② 补全 .js 扩展名进行加载
③ 补全 .json 扩展名进行加载
④ 补全 .node 扩展名进行加载
⑤ 加载失败,终端报错
如果传递给 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
当把目录作为模块标识符,传递给 require()
进行加载的时候,有三种加载方式:
① 在被加载的目录下查找一个叫做 package.json
的文件,并寻找 main
属性,作为 require()
加载的入口
② 如果目录里没有 package.json
文件,或者 main
入口不存在或无法解析,则 Node.js
将会试图加载目录下的index.js
文件。
③ 如果以上两步都失败了,则 Node.js
会在终端打印错误消息,报告模块的缺失:Error: Cannot find module 'xxx'