Node.js学习第二天,模块化,npm与包
文章目录
模块化的基本概念
什么是模块化
模块化是指解决一个复杂问题时 , 自顶向下 逐层把系统划分成若干模块的过程 , 对于整个系统来说,模块是可组合,分解和更换的单元
编程中的模块化
遵守固定的规则, 把一个大文件 拆分成独立并互相依赖的多个小模块
优点
- 提高了代码的重用性
- 提高了代码的可维护性
- 可以实现按需加载
模块化规范
模块化规范就是对代码进行模块化拆分与组合时, 需要遵守的那些规则
例如
- 使用什么样的语法格式来引用模块
- 在模块中使用什么样的语法格式向外暴露成员
**模块化规范的好处: ** 大家都遵守同样的模块化规范写代码 , 降低了沟通的成本 ,极大方便了各个模块之间的相互调用
Node.js中的模块化
Node.js中模块的分类
Node.js中根据模块的来源不同 , 将模块分为了3大类
- 内置模块 由Node.js官方提供的 , 例如 fs , path , http等,在安装Node.js时就已经将这些模块安装到本地了
- 自定义模块 用户创建的每个.js文件, 都是自定义模块 ;
- 第三方模块 第三方开发出来的模板 , 并非官方提供的内置模板 , 也不是用户创建的自定义模板,使用前需要先下载
加载模块
使用强大的require()
方法,可以加载需要的内置模块,用户自定义模块, 第三方模块进行使用
// 1. 加载内置模块
const fs = require('fs')
//2. 加载用户自定义模块
// 给出本地路径
const custom = require('./custom.js')
//3.加载第三方模块
const moment = require('moment')
注意 : 当使用require()
方法加载其他模块时, 会执行被加载模块中的代码
const custom = require('./myModule.js')
//在加载用户自定义模块时, 可以省略.js后缀名
//效果相同
const custom = require('./myModule')
console.log(custom)
Node.js中的模块作用域
什么是模块作用域
和函数作用域 类似, 在自定义模块中定义的变量 , 方法等成员 , 只能在当前的模块中访问 , 这种模块级别的访问限制,叫做模块的作用域
模块作用域的好处
防止 了全局变量污染的问题
例如:
在浏览器中, 没有模块作用域的概念
我导入first.js 模块, 里面的username=“张三”;
然后我又导入second.js模块,里面有username=“李四”
这时候,我打印console.log(username) 结果为"李四" ,
first.js中的username被污染
向外共享模块作用域中的成员
module对象
在每一个.js自定义模块中都有一个module对象, 他存储了当前模块有关的信息
console.log(module)
/*
Module {
id: '.',
path: 'D:\\test\\node',
exports: {},
filename: 'D:\\test\\node\\myModule.js',
loaded: false,
children: [],
paths: [
'D:\\test\\node\\node_modules',
'D:\\test\\node_modules',
'D:\\node_modules'
]
}
*/
module.exports 对象
在自定义模块中, 可以使用module.exports
对象,将模块内的成员分享出去, 供外界使用
外界使用requir()
方法导入自定义模块时, 得到的就是module.exports
所指的对象
在一个自定义模块中, module.exports
默认为空
// 当前文件就是用户自定义模块
const username = '张三'
function sayHello(){
console.log(`大家好,我是${username}`)
}
const age = 20
// console.log(module)
// 向module.exports对象上挂载属性
module.exports.username='赵6'
module.exports.myfunction=function(){
console.log('我是傻逼')
}
module.exports.sayHello=sayHello
module.exports.age=age
注意点 : 使用require()
方法导入模块时, 永远以module.exports
指向的最后一个对象为准
exports对象
由于module.exports
单词写起来比较复杂, 为了简化共享成员的代码,Node提供了exports
对象,默认情况下 , exports
和module.exports
指向同一个对象 , 最终的共享结果 , 还是以module.exports
指向的对象为准
只不过exports
写起来更加方便
console.log(exports === module.exports)
//结果为true
const username = '张三'
exports.username = username
exports.sayHello = function(){
console.log('大家好')
}
exports和module.exports使用误区
exports.username='张三'
module.exports ={
gender: 'boy',
age : 22
}
//在另一个模块中,导入该模块
const m = require('')
console.log(m)
//结果为
{gender: 'boy' , age:22}
原理: 原来exports
和module.exports
指向同一个对象 , 然后又在内存中开辟了一个新的对象, 让module.exports
对象指向这个新对象 , 由于require()
导入的最终结果以module.exports
为准, 所以就会出现这种情况
注意 : 为了防止混乱, 建议大家不要在同一个模块中同时使用exports
和module.exports
CommonJS 规范
Node.js 遵循了 CommonJS 模块化规范,CommonJS 规定了模块的特性和各模块之间如何相互依赖。
CommonJS规定
- 每个模块内部 , module变量 代表当前模块
- module 变量是一个对象 , 他的exports 属性(即 module.exports) 是对外接口
- 加载整个模块 , 其实就是加载该模块的
module.exports
属性 ,require()
用于加载模块
npm与包
包
Node.js中的第三方模块 又叫做 包
不同于Node.js中的内置模块与自定义模块 , 包由第三方个人或者团队开发出来, 免费提供给所有人使用
注意: Node.js中的包都是免费且开源的 , 不需要付费即可免费下载使用
如何下载包
国外有一家 IT 公司,叫做 npm, Inc. 这家公司旗下有一个非常著名的网站: https://www.npmjs.com/ ,它是全球最大的包共享平台,你可以从这个网站上搜索到任何你需要的包.
npm, Inc. 公司提供了一个地址为 https://registry.npmjs.org/ 的服务器,来对外共享所有的包,我们可以从这个服务器上下载自己所需要的包。
注意:
- 从 https://www.npmjs.com/ 网站上搜索自己所需要的包
- 从 https://registry.npmjs.org/ 服务器上下载自己需要的包
npm, Inc. 公司提供了一个包管理工具,我们可以使用这个包管理工具,从 https://registry.npmjs.org/ 服务器把需要的包下载到本地使用。
这个包管理工具的名字叫做 Node Package Manager(简称 npm 包管理工具),这个包管理工具随着 Node.js 的安装包一起被安装到了用户的电脑上。
大家可以在终端中执行 npm -v 命令,来查看自己电脑上所安装的 npm 包管理工具的版本号:
npm -v
当我们需要安装某个包时,执行这条命令即可
npm install 包名;
可以简写为
npm i 包名
当然我们也可以安装指定版本的包
npm i 包名@版本号
例如
npm i moment@2.22.2
初次下载
初次下载完包之后, 在项目文件夹下多了一个叫做node_modules
的文件夹和package-lock.json
的配置文件
node_modules
文件夹用来存放已安装项目中的包 ,require()
导入第三方包时, 就是从这个目录中查找并加载包package-lock.json
配置文件用来记录node_modules
目录下的每一个包的下载信息,例如包的名字 , 版本号 ,下载地址等
注意 : 我们不要手动修改node_modules
或package-lock.json
文件中的代码 , npm包管理器会自动去维护他们
包管理配置文件
npm规定 , 在项目根目录中 , 必须提供一个package.json
的包管理配置文件 , 用来记录与项目有关的一些配置文件, 例如
- 项目的名称 , 版本号 , 描述等
- 项目中都用到了那些包
- 哪些包只在开发期间用
- 哪些包在开发和部署时都需要用到
多人协作问题
例如 , 我们需要多人协作时 ,通常将项目上传到Github上,但是有一个问题:
整个项目体积30M , 第三方包体积是 28M , 源代码只有2M
第三方包的体积过大 , 不方便上传和下载
解决方案就是: 共享时剔除node_modules
文件夹 , 别人下载源代码后 , 只需要在本地安装包即可
记录项目中安装了哪些包
我们剔除了node_modules
文件夹 , 怎么知道项目中用到了哪些包呢?
在项目根目录下,创建一个叫做package.json
的配置文件 , 即可用来记录项目中用到了哪些包
快速创建pack.json
npm包管理工具提供了一个快捷命令 , 可以在执行命令所处的目录中 , 快速创建package.json这个包管理配置文件
初始化一个node项目
在执行命令所处的根目录中, 快速新建 package.json文件
npm init -y
注意
- 上述命令只能在英文目录下成功运行 , 所以项目文件夹的名称 一定要使用英文命令 ,不要使用中文,不能出现空格
- 运行
npm install 包
安装包时, npm包管理工具会自动包的名称和版本号 , 记录到package.json
中
dependencies节点
package.json
文件中 , 有一个dependencies
节点 , 专门用来记录npm install
命令安装了哪些包
一次性安装所有的包
当我们拿到一个剔除了node_modules
的项目后 , 需要先把所有的包下载到文件中 ,才能将项目运行起来否则会报错
可以通过命令把这个项目中要用到的包一次性安装下来
npm install
或 简写
npm i
执行此命令时 , npm包管理工具会先读取package.json
中的dependencies
节点, 读取到所有依赖包的名称和版本号后,npm包管理工具把这些包一次性下载到项目中
卸载包
可以运行指定的命令 , 来卸载指定的包
npm uninstall 包名
例如
npm uninstall moment
注意 : npm uninstall
命令执行成功后 , 会把卸载的包 ,从package.json
中的dependencies
中移除掉
devDependencies节点
如果某些包只在项目开发阶段会用到 , 在项目上线后不会用到 , 则建议把这些包记录到devDependencies
节点中 , 例如 webpack
工具 , 与之对应的 , 如果某些包在项目开发和上线后都需要用到 , 则建议把这些包记录到dependencies
节点中
可以执行如下命令 , 将包记录到devDependencies
节点中
npm install 包名 --save-dev
或简写为
npm i 包名 -D
包下载慢的问题
在使用npm包管理工具下载的时候 , 默认从国外的的 https://registry.npmjs.org/ 服务器进行下载,所以会很慢
我们可以修改npm的下载路径 , 使其从国内的镜像服务器上下载 , 这样就解决了包下载慢的问题
我们可以从淘宝NPM镜像服务器 上下载 , 淘宝的镜像服务器每隔一段时间会自动把npm服务器的包同步到自己的服务器上 , 对国内用于提供下载服务
//查看当前的下载包路径
npm config get registry
//默认为https://registry.npmjs.org/
//将包下载路径切换为淘宝镜像服务器
npm config set registry=https://registry.npm.taobao.org/
//检查镜像源是否修改成成功
npm config get registry
nrm
当我们使用上面的命令来切换npm下载镜像源 时 , 非常麻烦 , 我们可以使用一个小工具nrm
//将nrm 安装为全局可用的工具
npm i nrm -g
//查看所有的镜像源
nrm ls
//将下载包的镜像源切换为tabo
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
这三个属性 ,分别代表包的名字,版本号 ,包的入口main
属性用来指明包的入口文件, 即require()
需要引入的真正对象
模块加载机制
优先从缓存中加载
模块在第一次加载后会被缓存 , 这也就意味着多次调用require()
引入统一模块不会导致模块内的代码被执行多次
注意 : 不论是内置模块 , 用户自定义模块 , 还是第三方模块 ,他们都会优先从缓存中加载 , 从而提供模块的加载效率
内置模块的加载机制
内置模块是由Node.js官方提供的模块 , 内置模块的加载优先级最高
例如在node_moudles目录下有一个我自己创建的 fs模块
我现在引入 fs模块
require('fs')
实际引入的是系统内置模块的fs
自定义模块的加载机制
使用require()
加载自定义模块时 , 必须指定./
或../
开头的 路径标识符 , 如果没有指定./
或../
这样的路径标识符 , 则运行时会把他当做内置模块 或第三方模块进行加载
我们知道 , 在导入自定义模块时, 是可以省略.js
后缀名的 , 其实真正的原理是这样的
- 按照确切的文件名进行加载
- 如果上一步 行不通 , 则 补全
.js
拓展名进行加载 - 如果上一步行不通 , 则补全
.json
拓展名进行加载 - 如果上一步行不通 , 则补全
.node
拓展名进行加载 - 最终还是不行 , 则加载失败 , 报错
第三方模块的加载机制
如果传递给require()
的不是一个内置模块 , 也不是./
或../
开头的标识符, 则系统会判定为第三方模块 , 尝试从 /node_modules
文件夹中加载第三方模块
如果在当前项目的node_modules
文件夹中没有找到该模块 , 则开始逐层向上寻找 , 直到文件系统的根目录
目录作为模块
当我们给require()
传递的标识符是一个目录时 , 系统也会去加载 , 有三种加载方式
- 在被加载目录下查找一个叫做
package.json
的文件 , 并寻找main
属性 , 作为require()
加载的入口 - 如果在目录中没有
package.json
文件, 或者main
入口不存在或无法解析 , 则Node.js将会视图加载目录下的index.js
文件 - 如果上两步都失败了 , 则Node.js会报错
开发属于自己的包
-
新建一个项目文件夹 , 作为包的根目录
-
在包的根目录下 , 创建如下三个文件
package.json
包管理配置文件index.js
包的入口文件README.md
包的说明文档
-
初始化
package.json
配置文件 , 必须包含以下几个属性,不能少 -
编写
README.md
包的说明文档, 包括: 包的安装方式 , 导入方式 , 包内的方法和属性的使用说明 ,开源协议
发布包
-
注册
npm
账号 -
在终端登录
npm
账号(在运行npm login
命令之前, 必须保证包的下载地址为官方 , 可以使用nrm
修改下载地址,可以看前面的修改下载地址的文字)//执行 npm login //然后依次输入账户名和密码即可
-
cd
到包的根目录下 , 运行npm publish
命令 , 即可发布到npm官网上 (注意: 包名不能雷同 , 可以去官网去搜索有没有同名的包) -
发布包的时候要慎重 , 尽量不要去发布没有意义的包
删除已发布的包
在终端命令行登录后
运行命令npm unpublish 包名 --force
, 即可删除已发布的包
注意:
npm unpublish
命令只能删除72小时以内发布的包npm unpublish
删除的包 , 在24小时内不允许再次发布
声明
此篇文章根据 黑马程序员Node.js教学视频及相关课件 整理而来