Node.js-day02
模块化开发介绍
模块化基本概念
模块化:攒一台电脑(CPU、主板、内存、硬盘、显卡。。。) 活动板房:模块化;铺设铁道 编程中是否需要借助模块化的好处: 1、明显提高开发效率 2、方便后期的维护扩展(需要考虑成本) 模块化特点:高内聚,低耦合
模块化规范
早期,js中没有模块化的开发规范: 模块化要解决的问题:模块之间需要有隔离(解决命名冲突问题),并且模块之间需要有交互(解决模块之间的依赖问题)
- 模块化介绍
- 浏览器端模块化规范
- AMD —> requireJS
- CMD —> Sea.js
- 服务器端模块化规范
- CommonJS —> Node.js
- ES6语音本身提供了模块化 ESM
- Vue/React项目中会大量使用
- 浏览器端模块化规范
总结:
- 规范是一套标准说明
- 按照规范实现的库才可以用于开发
- 前面所学的核心模块fs/path/http都遵循CommonJS的规则
Node.js模块化
Node.js中,一个js文件就是一个模块,模块之间存在隔离,并且模块之间可以交互
- CommonJS模块化规则
- 导出模块成员
- module.exports = {}
- 导入模块内部成员
- require(参数),参数表示核心模块的名称或者自定义模块路径
- 导出模块成员
// common.js模块
function showInfo () {
console.log('nihao')
}
const PI = 3.14
// 如何导入模块的内部成员
module.exports = {
showInfo: showInfo,
PI: PI
}
// test.js 模块
/*
测试模块的导入
*/
// 导入核心模块
const path = require('path')
// 导入自定义模块(导入后,模块的代码会运行)
const obj = require(path.join(__dirname, 'common.js'))
obj.showInfo()
console.log(obj.PI)
总结:
- 导出的目的是让别的模块使用当前模块的功能
- 导入的目的是为了使用别的已经做好的模块
- 模块成员导出方式
- 最终导出以module.exports 为准
- exports是module.exports的别名
// 关于导出的具体细节
const msg = 'hello'
// module.exports = {
// msg: msg
// }
// -----------------------------
// module.exports的默认值是module.exports = {}
// module.exports.msg = msg
// module.exports.info = 'nihao'
// -----------------------------
// 如下的写法仅仅会导出info,因为msg被覆盖掉了
// module.exports.msg = msg
// module.exports = {
// info: 'nihao'
// }
// -----------------------------
// 如下的写法会导出两个属性
// module.exports = {}
// module.exports = {
// info: 'nihao'
// }
// module.exports.msg = msg
// 向对象里面添加属性时,取决于此时变量指向那个对象
// 不建议上述两种方式同时使用
// ====================================
// exports 是 module.exports 的别名,但是最终导出以 module.exports 为准
// module.exports = exports = {}
// exports.msg = 'hello'
// exports.info = 'nihao'
// ------------------------------------
// 如下的写法仅仅会导出一个属性info
exports.msg = 'hello'
// 下面的对象会覆盖默认的对象
module.exports = {
info: 'nihao'
}
// 上述两种方式不建议一块使用
总结:如果感觉上述导出规则很繁琐,那么可以仅仅记住如下一种即可 module.exports = { info: ‘nihao’ }
- 模块类型
- 内置核心模块:模块名称都是固定的 fs 、path、http
- .js模块:自定义模块
- .json模块:自定义模块,主要用来提供数据
- .node模块:自定义模块,这是二进制格式的,基于C/C++开发的模块,编译后形成该模块
总结:
- 为什么要采用模块化方式?提高开发效率(方便重用);方便后期维护和扩展
- 模块化是否需要标准规范?需要
- 都有哪些规范?AMD/CMD/CommonJS(Node.js)/ESM
- CommonJS的主要规则:模块的导出和导入规则
- 导出 module.exports/exports
- 导入 require(参数):参数要么是核心模块名称,要么是自定义模块路径
- 对于导出有一些细节需要注意:导出的多种写法
- 熟悉模块的类型
包
一个js文件是一个模块,该模块功能有限,一般一个模块只做一件事情
- 什么是包?
- 包可以看作是模块、代码 和 其它资源组合而成的一个更大的模块(第三方模块)
- 包可以实现更加复杂、完善的功能模块,方便代码的复用,提高开发效率。
- 如何复用包?
- 如何创建一个包?
- 包和模块之间是什么关系?包中可以包含更多模块
npm
npm介绍
npm: npm是一个网站,用于托管所有的包,方便大家发布包和搜索获取包 npm: 包管理工具的名字叫做Node.js Package Manager(简称 npm 包管理工具),这个包管理工具随着Node.js 的安装包一起被安装到了用户的电脑上。可以使用npm工具提供的命令管理包:包的下载安装、更新、卸载,发布…… 大家可以在终端中执行npm -v 命令,来查看自己电脑上所安装的npm 包管理工具的版本号:
- npm是一个网站,也是一个管理所有的包的工具
- 下载包的地址 https://registry.npmjs.org/
- 搜索包的地址 https://www.npmjs.com/
总结:npm既是一个网站,也是一个工具
npm基本操作
npm包管理工具:安装、更新、卸载…… 命令参考文档
- 安装包
# npm install 包名
# npm i 包名
# npm add 包名
npm install moment
# npm install 包名@版本号
npm i moment@2.25.3
- 卸载包
# npm uninstall 包名
# npm remove 包名
# npm un 包名
npm uninstall moment
npm remove moment
- 更新包
- 前提条件:项目的跟路径必须有一个package.json文件
- 默认更新到最新版本
# npm update 包名
# npm up 包名
npm update moment
总结:掌握包的基本安装、更新和卸载操作,熟悉相关命令的别名用法
创建一个包项目
- 创建项目目录 mypack
- 创建相关文件
- js模块 包的功能实现
- readme.md 包的使用说明
- package.json 在包的跟目录需要有这个文件,作用是描述包
- 进入目录执行如下命令,生成一个package.json文件
npm init -y
总结:至少需要有package.json文件 这个json的作用:可以记录当前项目所依赖的第三方包。
- 实现一个格式化日期的功能(基于moment包实现)
// 实现包的功能
const moment = require('moment')
// 封装一个格式化日期的方法供别人使用
// 函数的参数一表示需要格式化的时间(需要是日期对象)
// 函数的参数二表示需要的格式(yyyy-MM-dd hh:mm:ss)
function formatDate (date, style) {
return moment(date).format(style)
}
module.exports = {
formatDate: formatDate
}
- 测试代码
// 测试index.js文件的方法
const m = require('./index.js')
const d = m.formatDate(new Date(), 'yyyy-MM-DD HH:mm:ss')
console.log(d)
// -----------------------------
// 如果,我们的把发布到了npm网站上,别人可以按照如下方式使用
// 先安装包
// npm i mypack
// 再使用包提供的方法实现功能
// const m = require('mypack')
// const d = m.formatDate(new Date, 'yyyy-MM-DD')
总结:
- 实现包基本结构:至少要包含package.json文件
- 实现一个功能并导出
包管理配置文件
包管理配置文件介绍
npm 规定,在项目的根目录下,必须有一个包管理配置文件,名字叫做 package.json,用来记录项目中安装了哪些包。 注意:运行 npm install 命令安装包的时候,npm 包管理工具会自动把包的名称和版本号,记录到 package.json 中。
- 执行如下命令可以快速生成package.json配置文件
npm init -y
- 如果项目代码有依赖,但是没有node_modules目录是无法运行,需要先安装依赖,再运行
- 一次性安装所有的包
# 安装所有的依赖包
npm install
# 或者
npm i
总结:如果我们得到了别人的代码,一般代码中是不包含node_modules目录的,此时无法运行,需要通过npm i 命令安装所有的依赖后,才可以继续运行项目。
配置文件常用属性
- name 包的名称,全平台唯一
- version 包的版本号
- description 包的功能描述
- main 属性作用:包默认执行的js文件(包的入口)
- keywords 包被检索的关键字
- author 包的作者
- license 包遵循的开源协议
- dependencies (生产依赖 production)
- 如果某些包在开发和项目上线之后都需要用到,则建议把这些包记录到 dependencies 节点中。
- devDependencies (开发依赖 development )
- 如果某些包只在项目开发阶段会用到,在项目上线之后不会用到,则建议把这些包记录到devDependencies 节点中。(开发依赖 development)
npm i 包名 -D
# 或者
npm install 包名 --save-dev
- 如何仅仅安装生产依赖
- 默认npm install 会安装所有依赖(开发和生产依赖都安装)
- 通过—production 选项可以仅仅安装生产依赖
npm install --production
- scripts 属性的作用:定义一些脚本命令,可以起一个别名,方便运行
- 如果脚本的名称是start,那么运行时,可以简化为 npm start
"scripts": { "dev": "node 06-module-test-1.js"},
npm run dev
- package-lock.json文件的作用
- 记录包的依赖关系(记录所有的依赖顺序和版本等信息),防止版本更新导致的API变化的问题。
包的安装方式
- 本地安装:把包安装到当前项目的node_modules目录中,这种包主要用于提供开发相关API。
- 全局安装:把包安装到Node.js的安装目录中,这种包主要的作用是作为命令行的指令来用。
- 全局安装方式就是在安装时添加 -g 即可
- 卸载全局包时,也需要添加 -g
# 全局安装npm i i5ting_toc -g# 安装完成后使用命令i5ting_toc -f outline-day03-模块化开发.md -o
解决包下载慢的问题
- 为什么下载慢
在使用 npm 下包的时候,默认从国外的https://registry.npmjs.org/服务器进行下载,此时,网络数据的传输需要经过漫长的海底光缆,因此下包速度会很慢。
- 解决办法:使用国内镜像(副本)
淘宝在国内搭建了一个的服务器,专门把国外服务器上的包同步到国内的服务器,然后在国内提供下包的服务。从而极大的提高了下包的速度。 淘宝提供的下包服务叫做淘宝NPM 镜像,官网地址是:https://npm.taobao.org/,您可以参考官网提供的步骤,把下包的服务器从国外切换为国内的淘宝NPM 镜像。
- 切换淘宝镜像
# 安装包的时候指定一个镜像,每次都需要指定
npm i moment --registry=https://registry.npm.taobao.org/
# 查看当前下载包的镜像
npm config get registry
# 将下载包的镜像切换到淘宝镜像
npm config set registry=https://registry.npm.taobao.org/
- 使用nrm切换镜像
- 如果需要管理各种镜像,可以使用nrm命令行工具
- 使用nrm命令管理镜像
- nrm ls
- 查询所有镜像
- nrm use 镜像名称
- 切换镜像
- nrm ls
npm i nrm -g
开发自己的包
规范的包结构
在清楚了包的概念、以及如何下载和使用包之后,接下来,我们深入了解一下包的内部结构。
- 一个规范的包结构,需要符合以下3 点要求:
- 包必须以单独的目录而存在
- 包的顶级目录下要必须包含package.json 这个包管理配置文件
- package.json 中必须包含 name,version,main这三个属性,分别代表包的名字、版本号、包的入口。
- 注意:以上 3 点要求是一个规范的包结构必须遵守的格式,关于更多的约束,可以参考如下网址:
开发自己的包
- 需要实现的功能
- 格式化日期
- 项目初始化
- package.json
- index.js
- module.js
- README.md
- 实现功能
- 文档尽量写得完善
发布自己的包
- 注册 npm 账号
- 访问 https://www.npmjs.com/网站,点击 sign up 按钮,进入注册用户界面
- 填写账号相关的信息
- 点击 Create an Account 按钮,注册账号(邮箱会收到一封验证邮件,需要点击收到的链接,进行身份验证)
- 登录 npm 账号 (必须先切换到npm镜像)
- npm 账号注册完成后,可以在终端中执行npm login 命令,依次输入用户名、密码、邮箱后,即可登录成功。
- 把包发布到 npm 上
- 将终端切换到包的根目录之后,运行npm publish 命令,即可将包发布到 npm 上(注意:包名不能雷同)。
- 删除已发布的包
- 运行 npm unpublish 包名 —force 命令,即可从 npm 删除已发布的包。
发布包注意事项
① npm unpublish 命令只能删除 72 小时以内发布的包
② npm unpublish 删除的包,在 24 小时内不允许重复发布
③ 发布包的时候要慎重,尽量不要往npm 上发布没有意义的包!
模块与包加载机制
- 普通模块加载机制
模块在第一次加载后会被缓存。这也意味着多次调用require() 不会导致模块的代码被执行多次。 注意:不论是内置模块、用户自定义模块、还是第三方模块,它们都会优先从缓存中加载,从而提高模块的加载效率。
- 内置模块的加载机制
内置模块是由 Node.js 官方提供的模块,内置模块的加载优先级最高。 例如,require(‘fs’) 始终返回内置的 fs 模块,即使在 node_modules 目录下有名字相同的包也叫做fs。
- 自定义模块的加载机制
使用 require() 加载自定义模块时,必须指定以./ 或 ../ 开头的路径标识符。在加载自定义模块时,如果没有指定./ 或 ../ 这样的路径标识符,则node 会把它当作内置模块或第三方模块进行加载。
如果按确切的文件名没有找到模块,则Node.js 会尝试带上 .js、 .json 或 .node 拓展名再加载。
① .js 文件会被解析为JavaScript 文本文件
② .json 文件会被解析为 JSON 文本文件
③ .node 文件会被解析为通过 process.dlopen() 加载的编译后的插件模块
- 第三方模块(包)的加载机制
如果传递给 require() 的模块标识符不是一个内置模块,也没有以’./‘ 或 ‘../‘ 开头,则 Node.js 会从当前模块的父目录开始,尝试从它的/node_modules 目录里加载模块。如果没有找到,则移动到再上一层父目录,直到文件系统的根目录。
例如,假设在 ‘C:\Users\itheima\project\foo.js’ 文件里调用了 require(‘tools’),则Node.js 会按以下顺序查找:
① C:\Users\itheima\project\node_modules\tools
② C:\Users\itheima\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’
W