摘要:本文主要学习了Node的模块化,比较了CommonJS模块和ES模块的区别,以及缓存和分类。
环境
Windows 10 企业版 LTSC 21H2
Node 18.14.0
NPM 9.3.1
NVM 1.1.12
1 概念
模块是一个封装了特定功能的独立文件以便在其他文件中引入和使用,模块化是将一个复杂的程序文件依据一定规则或规范拆分成多个文件的过程。如果在开发时是按照模块开发的,那么整个项目就是模块化项目。
模块的内部数据是私有的,可以暴露内部数据让其他模块引用。
每个文件都可以作为一个模块,默认处于独立的作用域内。每个模块都包含一组相关的函数、对象或变量,通过这种方式,可以使用结构化的方法来管理大型应用程序,让代码更加清晰和易于维护。
Node支持以下几种模块:
- 内置模块:Node自带的模块,如fs、http、path等。
- 用户自定义模块:由开发者创建的模块。
- 第三方模块:通过命令安装的模块,如express、lodash等。
2 使用
在使用模块时遵循CommonJS模块规范,简称CJS模块,也支持ES模块规范,简称EJS模块。
2.1 CommonJS模块
2.1.1 导入模块
导入模块的方式:
1 | require('路径'); |
注意事项:
- 导入自己创建的模块时,导入时路径建议写相对路径,不能省略
./
和../
。 - 导入内置模块或第三方模块时,导入时直接写模块的名字即可,可以省略
./
和../
。
导入的文件类型说明:
- 导入JS和JSON文件时可以不用写后缀,导入C和C++编写的Node扩展文件也可以不写后缀,但是一般用不到。
- 导入其他类型的文件会以JS文件进行处理。
- 导入文件夹会先检测该文件夹下的
package.json
文件中main
属性对应的文件,如果对应的文件存在则导入,如果对应的文件不存在会报错。如果package.json
文件不存在,或者main
属性不存在,则会尝试导入文件夹下的index.js
文件和index.json
文件,如果还是没找到,就会报错。
如果要使用第三方模块,需要先试用npm install 模块名
命令进行安装,然后才能在代码里导入。
2.1.2 暴露模块
如果需要让外部模块使用模块内的数据,就需要将模块内的数据暴露出去。
模块暴露数据的方式有两种:
1 | module.exports = 数据; |
两种方式的区别:
module.exports
是导出对象的真正引用,exports
是导出对象的快捷方式。- 不能直接赋值
exports = 数据
,这样会导致断开exports
对module.exports
的引用。
如果暴露的属性名重复的话,后面的会覆盖前面的。
2.1.3 示例
暴露模块:
1 | let sex = '男'; |
导入模块:
1 | let tools = require('./tools.js'); |
2.2 ES模块
如果要在Node中使用ES模块,包括使用import
加载和export
输出,就需要将文件扩展名设置为mjs
表示这是一个ES模块,启用严格模式。
如果不想修改文件的扩展名,也可以在项目的package.json
文件中将type
字段指定为module
表示将JS都作为ES模块。一旦设置了以后,该目录里面的JS脚本都会被解释用ES模块。如果此时还想在项目中使用CommonJS模块,就需要将文件扩展名改为cjs
表示这是一个CommonJS模块。
注意,ES模块与CommonJS模块尽量不要混用,一个项目里面尽量统一一种规范。
2.3 比较
语法上面:
- CommonJS模块使用
require()
加载和module.exports
输出。 - ES模块使用
import
加载和export
输出。
用法上面:
- CommonJS模块的
require()
是同步加载,后面的代码必须等待这个命令执行完,才会执行。 - ES模块的
import
则是异步加载。或者更准确地说,ES模块有一个独立的静态解析阶段,依赖关系的分析是在那个阶段完成的,最底层的模块第一个执行。
加载方式:
- 扩展名为
mjs
的文件总是以ES模块加载。 - 扩展名为
cjs
的文件总是以CommonJS模块加载。 - 扩展名为
js
的文件加载取决于package.json
里面type
字段的设置,默认使用CommonJS模块加载。
2.4 混用
2.4.1 CommonJS模块加载ES模块
不能在CommonJS模块中使用require()
加载ES模块,否则会报错,可以使用import()
加载ES模块。
在mjs
文件中暴露ES模块:
1 | const sex = '男'; |
在js
文件CommonJS模块中使用import()
加载mjs
文件ES模块:
1 | (async () => { |
不能使用require()
加载ES模块的原因是CommonJS的模块是同步加载,而ES模块内部使用的是顶层await
命令,导致无法被同步加载。
2.4.2 ES模块加载CommonJS模块
ES模块的import
可以加载CommonJS模块。
在js
文件中暴露CommonJS模块:
1 | const sex = '男'; |
在mjs
文件ES模块中使用import
加载js
文件CommonJS模块:
1 | import tools from './tools.js'; |
3 缓存
Node会缓存已加载的模块,而不必每次都重新加载。
如果要重新加载模块,可以删除缓存:
1 | delete require.cache[require.resolve('./tools.js')]; |
4 分类
根据模块的来源、作用范围可以分为4钟类别:
- 内置模块:Node内置的模块,无需额外安装,通过
require(模块名)
引入。 - 自定义模块:开发者创建的模块,通过
require(路径)
引入。 - 本地模块:通过
npm install 模块
安装在本地node_modules
目录的第三方模块,通过require(模块名)
引入。 - 全局模块:通过
npm install 模块 -g
安装在全局node_modules
目录的第三方模块,通过require(模块名)
引入。
将内置模块以外的3种模块称为文件模块。
条