前端模块化机制
上古时代的宝贝
AMD (Async Module Definition)
AMD是浏览器端的模块化标准,它采用异步的方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在回调函数中,等到加载完成之后,这个回调函数才会运行。
CMD (Common Module Definition)
CMD是浏览器端的模块化标准,是在模块内部需要使用某个依赖时才去加载它。
UMD(Universal Module Definition)
UMD 是即通用模块定义,模块的代码结构通常会包含检测当前环境是浏览器环境还是Node环境的逻辑,然后根据不同的环境来定义模块的加载和导出方式。
AMD、CMD 和 UMD 已经成为较旧的模块化方案。在新项目中直接使用最新的 ESM 方案来实现模块化
现代模块化标准
CJS (CommonJS)
CommonJS 是Node端的模块化标准,一个文件就是一个模块,每个模块都有自己独立的作用域,模块内部定义的变量和函数不会自动暴露到全局环境中
- 模块加载机制
- 同步加载:CommonJS 的模块加载是同步的。当执行到
require
函数时,会阻塞当前代码的执行,直到被加载的模块的代码全部执行完毕并返回结果。 - 模块缓存:CommonJS 规范有一个模块缓存机制。当一个模块第一次被加载后,其结果会被缓存起来。如果后续再次使用
require
函数加载同一个模块,将直接返回缓存中的结果,而不会重新执行模块代码
- 同步加载:CommonJS 的模块加载是同步的。当执行到
- 模块的基本组成和使用方式
-
模块定义
- 在 CommonJS(CJS)中,每个文件都被看作是一个独立的模块。模块内部有自己的作用域,变量和函数不会自动暴露给外部。例如,一个简单的模块可以这样定义:
// mathUtils.js function add(a, b) { return a + b; } function subtract(a, b) { return a - b; } module.exports = { add: add, subtract: subtract };
- 这里定义了两个函数
add
和subtract
,并通过module.exports
将它们封装成一个对象,这个对象就是该模块向外暴露的接口。
-
-
模块引用
- 在需要使用这些模块功能的其他文件中,可以通过
require
函数来引入模块。例如:
// main.js const mathUtils = require('./mathUtils'); console.log(mathUtils.add(5, 3)); console.log(mathUtils.subtract(7, 2));
- 首先使用
require('./mathUtils')
来加载mathUtils.js
模块,require
函数返回的是mathUtils.js
中通过module.exports
暴露的对象。然后就可以通过这个对象访问mathUtils
模块中的函数。
- 在需要使用这些模块功能的其他文件中,可以通过
ESM (ES Module)
ES Module(ECMAScript Module)是 JavaScript 官方的标准化模块系统。它被引入到 ECMAScript 标准中,目的是为了在 JavaScript 语言层面提供一种统一、简洁且高效的模块管理方式,用于替代之前社区中各种非标准的模块规范(如 CommonJS、AMD 等),使得 JavaScript 在模块管理上更加规范和一致。
- 模块加载机制
- 浏览器端加载:ES Module 在浏览器中的加载是异步的,这意味着它不会阻塞浏览器的主线程。当浏览器请求模块文件时,其他脚本和页面的渲染等操作可以继续进行。例如,一个包含多个 ES Module 的复杂页面,各个模块可以在后台异步加载。浏览器会对加载过的模块进行缓存。如果同一个模块被多次引用,后续的引用将直接使用缓存中的模块,而不会再次发起请求。
- 在浏览器环境中,当遇到一个 ES Module 脚本标签(
<script type="module">
)时,浏览器会发起对该模块文件的请求。例如,对于<script type="module" src="main.js"></script>
,浏览器会先请求main.js
文件
- 在浏览器环境中,当遇到一个 ES Module 脚本标签(
- Node端加载: ES Module 在Node中的加载是同步的,Node.js 会按照模块依赖图的顺序加载模块,并且也有模块缓存机制。当一个模块被加载后,再次引用相同模块时会直接从缓存中获取。在加载过程中,有一些操作是异步的,比如读取文件内容(因为涉及到 I/O 操作)。但是Node.js 会处理这些异步操作,并且在模块的内容完全加载和解析完成之前,不会执行使用该模块的代码
- 在 Node.js 中,要使用 ES Module,文件扩展名必须明确指定(通常是
.mjs
),或者在package.json
文件中设置"type": "module"
。这是为了与 CommonJS 模块(默认是.js
文件)进行区分。例如,一个名为app.mjs
的文件可以使用 ES Module 语法。
- 在 Node.js 中,要使用 ES Module,文件扩展名必须明确指定(通常是
- 浏览器端加载:ES Module 在浏览器中的加载是异步的,这意味着它不会阻塞浏览器的主线程。当浏览器请求模块文件时,其他脚本和页面的渲染等操作可以继续进行。例如,一个包含多个 ES Module 的复杂页面,各个模块可以在后台异步加载。浏览器会对加载过的模块进行缓存。如果同一个模块被多次引用,后续的引用将直接使用缓存中的模块,而不会再次发起请求。
- 模块的基本组成和使用方式
- 模块定义
-
在一个 ES Module 文件中,可以使用
export
关键字来导出变量、函数、类等。例如// mathUtils.js export function add(a, b) { return a + b; } export function subtract(a, b) { return a - b; }
-
或者也可以使用命名空间导出的方式,将多个需要导出的内容放在一个对象中进行导出,如下:
// anotherMathUtils.js const multiply = (a, b) => a * b; const divide = (a, b) => a / b; export { multiply, divide };
-
- 模块的导入(import)
-
在需要使用这些模块的文件中,可以使用
import
语句来导入模块。例如,要使用上面定义的mathUtils.js
模块,可以这样做:// main.js import {add, subtract} from './mathUtils.js'; console.log(add(5, 3)); console.log(subtract(7, 2));
-
如果要使用命名空间导出的模块(如
anotherMathUtils.js
),可以这样导入:// anotherMain.js import {multiply, divide} from './anotherMathUtils.js'; console.log(multiply(4, 2)); console.log(divide(10, 2));
-
还可以使用别名来导入模块内容,这在避免命名冲突或者简化名称时很有用。例如:
// aliasMain.js import {add as sum, subtract as diff} from './mathUtils.js'; console.log(sum(3, 3)); console.log(diff(8, 1));` ```
-
- 模块定义