likes
comments
collection
share

Node.js 基础(2)- 模块化

作者站长头像
站长
· 阅读数 63

基本概念

模块化是指,在解决复杂问题的时候,自顶向下逐步将系统划分为若干模块的过程,对于整个系统来说,模块是可组合、分解和可更换的单元。 在编程领域中,一般遵循固定的规则,把一个大文件拆分成独立并且相互依赖的多个小模块。 提高代码的复用性、可维护性、按需加载。 可以理解为解决复杂问题时的拆分思想.

模块分类

根据模块来源不同,可以将 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 对象,里面存储了和当前模块有关的信息。

Node.js 基础(2)- 模块化

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 这个包:

Node.js 基础(2)- 模块化

<!-- 执行命令 -->
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;">你好! &copy;<span>小黄! </span></h1>'
const str = myTool.htmlEscape(htmlStr)

console.log(str)
// &lt;h1 style=&quot;color: red;&quot;&gt;你子! &amp;copy;&lt;span&gt;小黄! &lt;/span&gt;&lt;/h1&gti
  • 还原 html 字符串
// 1.导入自己的包
const myTool = require( 'wk-myTool' )
// -------功能3: 还原 HTML 中的特殊字符-------
const rawHTML = myTool.htmlUnEscape(str)

console.log(rawHTML)
// 输出 <h1 style="color: red;">你好! &copy;<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 "&lt;"
            case '>':
              return '&gt;'
            case '"':
              return '&quot;'
            case '&':
              return '&amp;'
        }
    })
}

function htmlUnEscape(htmlStr){
   
    return htmlStr.replace(/&lt;|&gt;|&quot;|&amp;/g, (match) => {
        switch (match) {
            case '&lt;':
               return "<"
            case '&gt;':
              return '>'
            case '&quot;':
              return '"'
            case '&amp;':
              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 会按以下顺序查找:

  1. C:\Users\test\project\nodemodules\tool.js
  2. C:\Users\test\nodemodules\tool.js
  3. C:\Users\nodemodules\tool.js
  4. C:\nodemodules\tool.js
  5. 找不到,终端报错
把目录作为模块加载

当把目录作为模块标识符,传递给 require() 进行加载的时候,有三种加载方式:

  1. 在被加载的目录下查找一个叫做 package.json 的文件,并寻找 main 属性,将 main 属性所指向的文件作为 require() 加载的入口
  2. 如果目录里没有 package.json 文件,或者 main 入口不存在或无法解析,则 Node.js 将会试图加载目录下的 index.js 文件.
  3. 如以上两步都失败了,则 Node.js 会在终端打印错误信息,报告模块的缺失: Error: Cannot find module'xox'
转载自:https://juejin.cn/post/7225535217339367479
评论
请登录