模块化是一种软件设计的概念,用于将大型软件系统分解为更小、可重用的模块或组件。
模块化的好处包括:
- 代码隔离和封装:模块化可以将代码分割为独立的模块,每个模块具有自己的作用域,避免了命名冲突和全局变量污染,增加了代码的可靠性和可维护性。
- 代码复用:模块化使得可以将可重用的代码抽象为模块,并在需要时进行引用和复用,减少了重复编写代码的工作量。
- 团队协作:模块化可以促进团队的协作开发,每个成员可以独立开发和维护各自的模块,降低了代码冲突和合并的风险。
- 性能优化:模块化可以实现按需加载,只加载所需的模块,减少了不必要的资源消耗,提高了应用程序的加载速度和性能。
由于一开始 JavaScript 本身没有提供模块化的机制,所以才会衍生出 CommonJS、AMD、ES Modules(ESM)和UMD这么多模块化规范。JavaScript 在 ES6 时原生提供了 import
和 export
模块化机制( ESModule
)。
早期并无 模块化 的概念,我们将所有的 js 文件放在一块,代码执行顺序就按照文件顺序执行。
<script src="./libs/jquery-3.6.0.min.js"></script>
<script src="./js/helper.js"></script>
<script src="./js/render.js"></script>
<script src="./js/index.js"></script>
这样做的缺点就是 污染全局作用域,每一个模块都是暴露在全局中的,容易产生 命名冲突,还要 手动处理各代码的依赖关系。
因此,我们使用自调用函数来编写模块化,其特点就是:在一个单独的函数作用域中执行代码,避免变量冲突。
(function () {
// 模块代码
})();
温馨提示:当然,在早期时候,一个函数 或者 一个对象也可以理解为一个模块 。
CommonJS 是 Node.js 最早采用的模块化规范。它于 2009 年发布,并在 Node.js 社区得到广泛应用。CommonJS 使用 require()
导入模块,使用 module.exports
或 exports
导出模块。
在CommonJS中,一个文件就是一个模块,内部定义的变量就属于这个模块里的,不会对外暴露,所以不会污染全局变量。
CommonJS 同步加载 模块,等当前模块加载完成了才进行下一步,服务器端文件都是保存在硬盘上,所以同步加载没问题。但是浏览器上,需要把文件从服务器端请求过来,比较慢,所以同步加载不适合用在浏览器上。
@API
- 导出:
module.exports = {}
|exports.xxx = 'xxx'
- 导入:
require("path")
@Examples
module.exports = {
foo: 'Hello',
bar: 30,
};
exports.foo = 'Hello';
exports.bar = 30;
const moduleA = require('./moduleA');
console.log(moduleA); // { foo: 'Hello', bar: 30 }
提示:
exports
是对module.exports
的引用,不能直接给exports
赋值,直接赋值无效,结果是一个空对象,module.exports
可以直接赋值。- 一个文件不能写多个
module.exports
,如果写多个,对外暴露的接口是最后一个module.exports
。- 模块如果没有指定使用
module.exports
或者exports
对外暴露接口时,在其他文件就引用该模块,得到的是一个空对象。
由于 commonJS 规范不适用于浏览器,因为要从服务器加载文件,不能用同步模式,所以有了AMD(Asynchronous Module Definition,异步模块定义)规范。AMD是一种异步模块定义规范,于 2011 年由 Dojo 社区提出。它主要用于浏览器环境,支持动态加载模块。AMD 使用 define()
来定义模块,使用 require()
来异步加载模块。
依赖前置,require([dep1, dep2], callback),先加载依赖再执行回调函数。
优点是可以在浏览器环境中 异步加载 模块,而且可以 并行加载 多个模块。
目前,主要有两个Javascript 库实现了AMD规范:
@API
- 定义模块:
- 有依赖:
define(['module1', 'module2'], (m1, m2) => { return 模块; })
- 无依赖:
define(() => { return 模块 })
- 有依赖:
- 引入:
require(['module1', 'module2'], (m1, m2) => { 使用m1, m2模块 })
@示例
// A.js
define(function () {
const splitBirth = (idCard) => {
if (isNaN(idCard) || idCard.length !== 18) {
return null;
}
return {
year: idCard.slice(6, 10),
month: idCard.slice(10, 12),
day: idCard.slice(12, 14),
};
};
return {
splitBirth,
};
});
// B.js
define(["./A.js"], function (A) {
const getBirth = (idCard) => {
const obj = A.splitBirth(idCard);
if(obj) {
return `出生年月:${obj.year}年${obj.month}月${obj.day}日`;
}
return "";
};
return {
getBirth
}
});
// main.js
require(["./utils/B.js"], (B) => {
const birth = B.getBirth("510321199307161234");
console.log(birth); // 出生年月:1993年07月16日
});
CMD(Common Module Definition)是一种用于在浏览器环境下实现模块化的规范,与 AMD 类似,但有些细微的差异.
-
定义模块: 使用
define
函数定义模块。define
函数接受一个工厂函数作为参数,工厂函数中可以通过require
函数引入依赖的模块,并通过module.exports
导出模块的内容。例如:define(function(require, exports, module) { // 引入依赖模块 var dependency1 = require('dependency1'); var dependency2 = require('dependency2'); // 导出模块内容 exports.foo = 'Hello'; exports.bar = 30; });
-
导入模块: 使用
require
函数导入模块。require
函数接受一个模块名称作为参数,并返回该模块的导出内容。例如:var moduleA = require('moduleA'); var moduleB = require('moduleB'); // 使用导入的模块
-
加载器配置: 在使用 CMD 之前,需要配置相应的加载器(如 Sea.js)来处理模块的加载和依赖关系。加载器通常需要在页面中引入,并通过配置选项指定模块的路径和别名等信息。
CMD 的使用方式与 AMD 类似,都是通过异步加载模块来实现模块化。CMD 更加强调 按需加载 和 延迟执行,它的模块定义和导入方式相对更加简洁和直观。CMD 在国内的前端开发中比较常见,特别是在大型项目和模块化开发中有一定的使用率。
严格上说,UMD(Universal Module Definition) 不能算是一种模块规范,因为它没有模块定义和调用,CommonJS、AMD、CMD并行的状态下,就需要一种方案能够兼容他们,这样我们在开发时,就不需要再去考虑依赖模块所遵循的规范了,而UMD的出现就是为了解决这个问题。
ES Modules 是 ECMAScript(JavaScript)的官方模块化规范,自 ES6(ES2015)开始引入。它于 2015 年发布,并逐渐成为主流的模块化规范。ES Modules 使用 import
导入模块,使用 export
导出模块,可以在浏览器和 Node.js 环境中使用。
机制:ES6的模块机制在依赖模块时并不会先去预加载整个脚本,而是生成一个只读引用,并且静态解析依赖,等到执行代码时,再去依赖里取出实际需要的模块。
特点:编译时加载,不允许在里边引用变量,必须为真实的文件路径。可以通过调用 import() 语句,会生成一个promise去加载对应的文件,这样子就是运行时加载,可以在路径里边编写变量
缺点:浏览器暂不支持,需要babel编译过
@API
@示例
导出单个属性
export const a = 10;
export const b = 20;
import { a, b } from './module.js';
导出属性列表
const a = 10;
const b = 20;
export { a, b };
import { a, b } from './module.js';
重命名导出
export { a as x, b as y };
import { x, y } from './module.js';
默认导出
export default { foo: 'bar' };
import module from './module.js';
console.log(module.foo);
导入所有
import * as xx from './module.js';
# | IIFE |
CommonJS |
AMD |
CMD |
ES6 |
---|---|---|---|---|---|
实现 | 浏览器端 | 服务端 | 浏览器端 | 浏览器端 | 浏览器端 |
加载方式 | 顺序加载 | 同步加载(运行时) | 异步加载(依赖前置) | 按需加载/延迟加载 | 编译时 |
库 | / | / | requirejs >> | seajs >> | / |
来源 | 前端社区 | 前端社区 | 前端社区 | 前端社区 | 官方 |
是否编译 | 否 | 否 | 否 | 否 | 是 |