Node.js 基础(2)- 模块化
基本概念
模块化是指,在解决复杂问题的时候,自顶向下逐步将系统划分为若干模块的过程,对于整个系统来说,模块是可组合、分解和可更换的单元。 在编程领域中,一般遵循固定的规则,把一个大文件拆分成独立并且相互依赖的多个小模块。 提高代码的复用性、可维护性、按需加载。 可以理解为解决复杂问题时的拆分思想.
模块分类
根据模块来源不同,可以将 Node.js中的模块分为三类:
- 内置模块:Node.js 官方提供的
- 自定义模块:用户创建的每个 js 文件都是自定义模块
- 第三方模块:由第三方开发的模块,非官方提供的内置模块,也不是用户自己创建的自定义模块,使用前需要先下载,
模块加载
任何种类的模块都可以使用 require()
方法进行加载引用。
// 内置模块
const fs = require("fs")
// 自定义模块
const custome = require("./custome.js")
// 第三方模块
const moment = require("moment")
注意:
- 在使用 require() 方法加载其他模块的时,会执行被加载模块的代码。
- 在使用 require() 方法用户自定义模块时,可以省略 .js 后缀名。
// custome.js
console.log("custome is require")
// test.js
const custome = require("./custome.js")
console.log(custome)
// 输出:
// custome is require
// {}
模块作用域
与函数作用域相似,在自定义模块中的变量、方法等成员,只能在当前模块中被访问,这种模块级别的访问限制叫做模块作用域。 好处:防止了全局变量的污染问题。
// custome.js
const userName = "kelly"
function sayHello(){
console.log("Hello, my name is" + userName)
}
// test.js
const custome = require("./custome")
console.log(custome) // {}
模块作用域成员共享
module 对象:每个 js 自定义模块,都有一个 module 对象,里面存储了和当前模块有关的信息。
module.exports 对象可以将模块内的成员共享出去,供外部使用,在使用 require() 方法导入自定义模块时,得到的就是 module.exports 所指向的对象。 在自定义模块中,module.exports 默认指向空对象。
// custome.js
module.exports.userName = "kelly";
module.exports.sayHello = function(){
console.log("hello")
}
// test.js
const custome = require("./custome")
console.log(custome)
// { userName: 'kelly', sayHello: [Function (anonymous)] }
注意,使用 require() 导入模块时,永远以 module.exports 指向的对象为准。
// custome.js
module.exports.userName = "kelly";
module.exports.sayHello = function(){
console.log("hello")
}
module.exports = {
nickName: "alice",
sayHi: function(){
console.log("hi")
}
}
// test.js
const custome = require("./custome")
console.log(custome)
// { nickName: 'alice', sayHi: [Function: sayHi] }
exports 对象: 由于 module.exports 单词写起来比较复杂,为了简化向外共享成员的代码,Node 提供了 exports 对象,默认情况下,module.exports 和 exports 指向的是同一个对象,最终共享结果以 module.exports 为准。
// exports.js
console.log(exports) // {}
console.log(module.exports) // {}
console.log(exports === module.exports) // true
exports.userName = "aa"
exports.sayHello = function(){
console.log("hello")
}
// test.js
const exportData = require("./exports")
console.log(exportData)
// { userName: 'aa', sayHello: [Function (anonymous)] }
需要注意的是,require() 模块的时候,得到的永远是 module.exports 指向的对象。
**** egs1
// exportTest.js
exports.userName = "bb"
module.exports = {
gender: "男"
}
// test.js
const exportData = require("./exports")
console.log(exportData)
// { gender: "男" }
**** egs2
// exportTest.js
module.exports.userName = "bb"
exports = {
gender: "男"
}
// test.js
const exportData = require("./exports")
console.log(exportData)
// { userName: "bb" }
**** egs3
// exportTest.js
module.exports.userName = "bb"
exports.gender = "男"
// test.js
const exportData = require("./exports")
console.log(exportData)
// { userName: "bb", gender: "男" }
**** egs4
// exportTest.js
exports = {
gender: "男",
userName: "bb"
}
module.exports = exports;
module.exports.age = 12;
// test.js
const exportData = require("./exports")
console.log(exportData)
// { userName: "bb", gender: "男", age:12 }
模块化规范
模块化规范是指,对代码进行拆分和组合的时候需要遵循的规则。如,使用什么样的语法格式引用模块,在模块中使用什么样的语法规范向外暴露成员。 大家都遵守同样的规范写代码,降低了沟通成本,方便各个模块之间相互调用。
Node.js 遵循了 CommonJs 模块化规范,CommonJs 规定了模块的特性以及各个模块之间如何实现相互依赖。
CommonJs 规范
- 在每个模块内部,module 代表当前模块。
- module 变量是一个对象,它的 exports 属性是对外的接口(即 module.exports)。
- 加载某个模块时,其实是加载某个模块的 module.exports 属性,require() 方法用于加载模块。
NPM 与 包
一、包基本概念
1. 什么是包
Node.js 中的第三方模块又叫做包。
2. 包的来源
由第三方或者个人团队开发出来的,免费供所有人使用(免费且开源)。
3. 包的作用
Node.js 内置模块仅提供了一些底层的 API ,导致在基于内置模块进行项目开发时,开发效率低。包是基于内置模块封装出来的,提供了更高级、更方便的 API,提高了开发效率。包与内置模块之间的关系类似于 jQuery 与 浏览器内置 API 之间的关系。
4. 包的下载
www.npmjs.com/, 全球最大的包共享平台(npm.inc 公司旗下的网站),可以在这个网站上搜索到任何你想要的包。
npm.inc 公司通过 registry.npmjs.org 服务器对外共享所有的包,我们可以通过这个服务器进行下载自己所需要的包。
我们可以通过 npm.inc 公司提供的 Node Pacakage Manager(npm 包管理工具) 从 registry.npmjs.org 服务器下载所需要的包,这个包管理工具简称 NPM, 随着 Node.js 安装包一起安装到电脑上,不需要额外下载。可以通过 npm -v
, 查看电脑安装的 npm 版本。
二、npm 初体验
1. 自定义时间工具包进行时间格式化
// 自定义包 dateFormat.js
function dateFormat (dateStr){
const dt = new Date(dateStr);
let year = dt.getFullYear();
let month = paddingZero(dt.getMonth());
let day = paddingZero(dt.getDate());
let hours = paddingZero(dt.getHours());
let minutes = paddingZero(dt.getMinutes());
let seconds = paddingZero(dt.getSeconds());
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
// 定义补零
function paddingZero(num){
return num > 9 ? num : "0"+num
}
module.exports = {
dateFormat
}
// dateTest.js
const TIME = require("./dateFormat")
const time = new Date();
console.log("未格式化时间:",time)
const dataStr = TIME.dateFormat(time)
console.log("已格式化时间",dataStr)
2. 通过第三方包实现时间日期格式化
第三方包的安装 npm install 包名
// 安装 moment : npm i moment
// dateTest.js
const moment = require("moment")
const dateStr = moment().format("YYYY-MM-DD HH-mm:ss")
console.log("已格式化时间",dateStr)
初次安装完包之后,在我们项目文件夹下会多一个 node_modules 的文件夹和一个 package-lock.json 的配置文件。
node_modules 文件夹用来存放所有已安装到项目中的包,require() 导入第三方包时,就是从这个目录查找并加载包的。
package-lock.json 配置文件用来记录 node_modules 目录下每一个包的下载信息,比如包的名字、版本号、下载地址等。
默认情况下,我们通过 npm install
命令安装的都是最新版本的包,如果想要安装指定版本的包,可以通过 @
指定包的版本号。如:npm i moment@2.22.2
。
三、包的其他概念
1.包的语义版本规范
包的版本号是以"点分十进制"进行定义的,共有三位数字,如:"2.12.1"。版本号的第一位数字表示包的大版本,第二位数字表示包的功能版本,第三位数字表示 bug 修复版本。
2.包版本的提升规则
只要前面的版本号增长了,后面的版本号归零。如:2.12.3 -> 2.13.0、2.12.3 ->3.0.0。
3.包管理配置文件
npm 规定, 在项目根目录中必须提供一个 package.json 的包管理配置文件,用来记录与项目相关的一些配置信息。如:项目名称、版本号、描述;项目中引用的包名; 那些包在开发期间使用、哪些包在开发和部署时使用等。
// package.json
{
"dependencies": {
"moment": "^2.29.4"
}
}
快速创建 package.json 文件: npm init -y
。
// 新建项目文件夹之后 执行 npm init ,在执行命令的目录中快速创建 package.json 文件
npm init -y
// package.json
{
"name": "node-studyn",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "https://gitee.com/taishangaicai/node-studyn.git"
},
"keywords": [],
"author": "taishangaicai",
"license": "ISC"
}
// 注意:
// 命令只能在英文的目录下成功运行! 所以,项目文件夹的名称一定要使用英文命名,不要使用中文,不能出现空格.
// 运行npm install 命今安装包的时候,npm 包管理工具会自动把包的名称和版本号,记录到 package.json 中
dependencies 节点: 来记录使用 npm install命令安装了哪些包
// 一次性安装多个包 用空格隔开
npm i jquery moment
// package.json
{
"name": "node-studyn",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "https://gitee.com/taishangaicai/node-studyn.git"
},
"keywords": [],
"author": "taishangaicai",
"license": "ISC",
"dependencies": {
"jquery": "^3.6.4",
"moment": "^2.29.4"
}
}
4.包的批量安装与卸载
一个性安装已有项目中所有的包:npm install
, 执行 npm install
命今时, npm 包管理工具会先读取 package.json 中的 dependencies ,读取到记录的所有依赖包名称和版本号之后,npm 包管理工具会把这些包一次性下载到项目中。
可以运行 npm uninstall
命令,来卸载指定的包。npm uninstall 命令执行成功后,会把卸载的包,自动从 package.ison 的 dependencies 中移除掉。
npm uninstall moment
5.devDependencies 节点
如果某些包只在项目开发阶段会用到,在项目上线之后不会用到,则建议把这些包记录到 devDependencies 节点中;
与之对应的,如果某些包在开发和项目上线之后都需要用到,则建议把这些包记录到 dependencies 节点中; 可以使用如下的命令,将包记录到 devDependencies 节点中:
// 安装指定的包,并记录到 devDependencies 节点中
npm i 包名 -D
// 注意:上述命令是简写形式,等价于下面完整的写法:
npm install 包名 --save-dev
对于一个包是否需要安装在 devDependencies 节点中,可以在 npmjs 中搜索查看教程, 如 webpack 这个包:
<!-- 执行命令 -->
npm i webpack -D
<!-- 执行结果 -->
{
"name": "node-studyn",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "https://gitee.com/taishangaicai/node-studyn.git"
},
"keywords": [],
"author": "taishangaicai",
"license": "ISC",
"dependencies": {
"jquery": "^3.6.4",
"moment": "^2.29.4"
},
"devDependencies": {
"webpack": "^5.79.0"
}
}
6.包下载缓慢问题
原因:在使用 npm 下包的时候,默认从国外的 registry.npmis.org/ 服务器进行下载,此时,网络数据的传输需要经过漫长的海底光缆,因此下包速度会很慢。
解决:使用淘宝 npm 镜像服务器。淘宝在国内搭建了一个服务器,专门把国外官方服务器上的包同步到国内的服务器,然后在国内提供下包的服务从而极大的提高了下包的速度。
切换 npm 的下包镜像源:
# 查看当前的下包镜像源
npm config get registry
// http://registry.npmjs.org/
#将下包的镜像源切换为淘宝镜像源
npm config set registry=https://registry.npm.taobao.org/
# 检查镜像源是否下载成功
npm config get registry
7.包的分类
使用 npm 包管理工具下载的包,共分为两大类,分别是:
- 项目包
- 全局包
1. 项目包
那些被安装到项目的 node modules 目录中的包,都是项目包。项目包又分为两类,分别是:
开发依赖包 (被记录到 devDependencies 节点中的包,只在开发期间会用到) 核心依赖包(被记录到 dependencies 节点中的包,在开发期间和项目上线之后都会用到)
npm i 包名 -D // 开发依赖包 (会被记录到 devDependencies 节点下)
npm i 包名 // 核心依赖包 (会被记录到 dependencies 节点下)
2. 全局包
在执行 npm install 命令时,如果提供了 -g 参数,则会把包安装为全局包。
npm i 包名 -g // 全局安装指定的包
npm uninstall 包名 -g // 卸载全局安装的包
注意:
- 只有工具性质的包,才有全局安装的必要性。因为它们提供了好用的终端命令
- 判断某个包是否需要全局安装后才能使用,可以参考官方提供的使用说明即可.
i5ting_toc
把 md 文档转为 html页面的小工具,使用步骤如下:
<!-- # 将 i5ting_toc 安装为全局包 -->
npm install -g i5ting_toc
<!-- # 用 i5ting toc,轻松实现 md 转 html 的功能 -->
i5ting_toc -f 要转换的md文件路径 -o
8.规范的包的结构
在清楚了包的概念、以及如何下载和使用包之后,接下来,我们深入了解一下包的内部结构。 一个规范的包,它的组成结构,必须符合以下 3 点要求:
- 包必须以单独的目录而存在
- 包的顶级目录下要必须包含 package.json 这个包管理配置文件
- package,json 中必须包含 name,version,main 这=个属性,分别代表包的名字、版本号、包的入口
注意:以上 3 点要求是一个规范的包结构必须遵守的格式,关于更多的约束,可以参考如下网址:yarnpkg.com/zh-Hans/doc…
四、开发自己的包
1.功能设计
- 转化时间
<!-- 1.导入自己的包 -->
const myTool = require( 'wk-myTool' )
myTool.dateFormat(new Date()) // 2023-04-18 20:18:30
- 转译 html 为字符串
<!-- 1.导入自己的包 -->
const myTool = require( 'wk-myTool' )
<!-- // ------- 功能2: 转义 HTML 中的特殊字符----- -->
const htmlStr = '<h1 style="color: red;">你好! ©<span>小黄! </span></h1>'
const str = myTool.htmlEscape(htmlStr)
console.log(str)
// <h1 style="color: red;">你子! &copy;<span>小黄! </span></h1>i
- 还原 html 字符串
// 1.导入自己的包
const myTool = require( 'wk-myTool' )
// -------功能3: 还原 HTML 中的特殊字符-------
const rawHTML = myTool.htmlUnEscape(str)
console.log(rawHTML)
// 输出 <h1 style="color: red;">你好! ©<span>小黄! </span></h1>
2.代码实现
初始化包基本结构
- 新建 my-test-tools 文件夹,作为包的根目录
- 在 my-test-tools 文件夹中,新建如下三个文件: package.json (包管理配置文件)、 index.js(包的入口文件)、 README.md(包的说明文档)。
主要代码
// 入口文件 index.js
/**
* @description 格式化时间
*/
function dateFormat(dateStr){
const dt = new Date(dateStr);
let year = dt.getFullYear();
let month = paddingZero(dt.getMonth() + 1);
let day = paddingZero(dt.getDate());
let hours = paddingZero(dt.getHours());
let minutes = paddingZero(dt.getMinutes());
let seconds = paddingZero(dt.getSeconds());
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
// 定义补零
function paddingZero(num){
return num > 9 ? num : "0"+num
}
function htmlEscape(htmlStr){
return htmlStr.replace(/<|>|"|&/g, (match) => {
switch (match) {
case '<':
return "<"
case '>':
return '>'
case '"':
return '"'
case '&':
return '&'
}
})
}
function htmlUnEscape(htmlStr){
return htmlStr.replace(/<|>|"|&/g, (match) => {
switch (match) {
case '<':
return "<"
case '>':
return '>'
case '"':
return '"'
case '&':
return "&"
}
})
}
module.exports = {
dateFormat,
htmlEscape,
htmlUnEscape,
}
3.发布包
1.注册 npm 账户:
- 访问www.npmjs.com/ 网站,点击 sign up 按钮,进入注册用户界面
- 填写账号相关的信息: Full Name、Public Email、Username、Password
- 点击 Create an Account 按钮,注册账号
- 登录邮箱,点击验证链接,进行账号的验证
2.在终端登录 npm 账号
npm 账号注册完成后,可以在终端中执行 npm login
命令,依次输入用户名、密码、邮箱后,即可登录成功。
注意: 在运行 npm login 命令之前,必须先把下包的服务器地址切换为 npm 的官方服务器。否则会导致发布失败!
3.把包发布到npm上
将终端切换到包的根目录之后,运行 npm publish 命令,即可将包发布到 npm 上(注意: 包名不能雷同)
4.删除已发布的包
运行 npm unpublish 包名 --force
命令,即可从 npm 删除已发布的包.
注意:
- npm unpublish 命令只能删除 72 小时以内发布的包
- npm unpublish 删除的包,在 24小时内不允许重复发布
- 发布包的时候要慎重,尽量不要往 npm 上发布没有意义的包!
模块加载机制
优先从缓存中加载
横块在第一次加载后会被缓存。 这也意味着多次调用 require() 不会导致模块的代码被执行多次。
注意:不论是内置模块、用户自定义模块、还是第三方模块,它们都会优先从缓存中加载,从而提高模块的加载效率。
// moduleRequire.js
console.log("moduleRequire")
// moduleRequireTest.js
const moduleRequire = require("./moduleRequire")
const moduleRequire = require("./moduleRequire")
const moduleRequire = require("./moduleRequire")
// 引入三次 只输出一次 moduleRequire
内置模块加载机制
内置模块是由 Node.js 官方提供的模块,内置模块的加优先级最高,例如,require(fs)
始终返回内置的 fs 模块,即使在 node_modules 目录下有名字相同的包也叫做 fs。
自定义模块的加载机制
使用 require()
加载自定义模块时,必须指定以 ./
或 ../
开头的路径标识符。
在加载自定义模块时,如果没有指定./
或 ../
这样的路径标识符,则 node 会把它当作内置模块或第三方模境进行加载。
同时,在使用 require()
导入自定义模块时,如果省略了文件的扩展名,则 Nodejs 会顺序分别尝试加载以下的文件:
- 按照确切的文件名进行加载
- 补全 .js 扩展名进行加载
- 补全 .json 扩展名进行加载
- 补全 .node 扩展名进行加载
- 加载失败,终端报错
第三方模块加载机制
如果传递给 require()
的模块标识符不是一个内置模块,也没有以 ./
或 ../
这样的路径标识符开头,则 Node.js 会从当前模块的父目录开始,尝试从 /node modules
文件夹中加载第三方模块。
如果没有找到对应的第三方模块,则移动到再上一层父目录中,进行加载,直到文件系统的根目录。
例如,假设在 C\Users\test\project\foo.js
文件里调用了 require(tool)
,则 Node.js 会按以下顺序查找:
- C:\Users\test\project\nodemodules\tool.js
- C:\Users\test\nodemodules\tool.js
- C:\Users\nodemodules\tool.js
- C:\nodemodules\tool.js
- 找不到,终端报错
把目录作为模块加载
当把目录作为模块标识符,传递给 require()
进行加载的时候,有三种加载方式:
- 在被加载的目录下查找一个叫做 package.json 的文件,并寻找 main 属性,将 main 属性所指向的文件作为 require() 加载的入口
- 如果目录里没有 package.json 文件,或者 main 入口不存在或无法解析,则 Node.js 将会试图加载目录下的 index.js 文件.
- 如以上两步都失败了,则 Node.js 会在终端打印错误信息,报告模块的缺失: Error: Cannot find module'xox'
转载自:https://juejin.cn/post/7225535217339367479