前端模块化理解

木头的喵喵拖孩

持续补充中…

模块化是为了更高效地复用代码,减少重复代码,提高代码的可读性,可维护性,可复用性。
javascript 中的模块化有多种实现方式,本文主要介绍 CommonJS、AMD、CMD、ES6 模块化以及 UMD(通用模块定义)。

模块化分类

按端分类

  • 服务端
    CommonJS
  • 浏览器端
    AMD、CMD、ES6 Module

按同步异步分类

  • 同步
    CommonJS
  • 异步
    AMD、CMD、ES6 Module

按是否可以条件加载分类

  • 可以条件加载
    CommonJS、AMD、CMD
  • 不能条件加载
    ES6 Module

CommonJS(CJS)

CommonJS 是服务器端模块化的一种实现方式,最初是由 Node.js 团队实现的,它通过 require()和 exports()来实现模块的加载和使用。

特点

  • 在服务端使用
  • 导出的模块为原始值的复制或深拷贝(重点)

代码

目录结构:

  • main.js
  • module1.js
  • module2.js
1
2
3
4
5
6
7
8
// module1.js
let name1 = "模块1";
function introduce1() {
console.log(`我是${name1}`);
}

// exports = { name1, introduce1 }; // 导出方式1,不建议使用
module.exports = { name1, introduce1 }; // 导出方式1
1
2
3
4
5
6
7
8
// module2.js
let name2 = "模块2";
function introduce2() {
console.log(`我是${name2}`);
}

// exports = { name2, introduce2 }; // 导出方式1,不建议使用
module.exports = { name2, introduce2 }; // 导出方式1
1
2
3
4
5
6
7
8
9
// main.js
// const module = require('./module.js'); // 引入方式1
const { name1, introduce1 } = require("./module1.js"); // 引入方式2
console.log(name1);
introduce1(); // 我是模块1

const { name2, introduce2 } = require("./module2.js");
console.log(name2);
introduce2(); // 我是模块2

AMD

AMD 是浏览器端模块化的一种实现方式,最早是由 RequireJS 团队实现的,它通过 define()和 require()来实现模块的加载和使用。
RequireJS 官网

特点

  • 不能按需加载(和 CMD 相比)
    AMD 的依赖只要声明了就会被加载

代码

目录结构:

  • index.html
  • js
    • main.js
    • require2.3.6.js
    • lib
      • module1.js
      • module2.js
1
2
3
4
5
6
7
8
9
10
11
12
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script src="js/require2.3.6.js" data-main="js/main"></script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// main.js
require.config({
baseUrl: "js/lib",
// 配置一些模块引用别名
// paths: {
// "module3": "a/b/c/module3.long.name.min", //实际路径为js/lib/a/b/c/module3.long.name.min.js
// }
});

require(["module1"], function (module1) {
console.log(module1.name1);
module1.introduce1();
module1.myFriend();
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// module1.js
define(["module2"], function (module2) {
let name1 = "模块1";
function introduce1() {
console.log(`我是${name1}`);
}
function myFriend() {
console.log(`我是${name1},我的朋友是${module2.name2}`);
}
return {
name1,
introduce1,
myFriend,
};
});
1
2
3
4
5
6
7
8
9
10
11
// module2.js
define(function () {
let name2 = "模块2";
function introduce2() {
console.log(`我是${name2}`);
}
return {
name2,
introduce2,
};
});

CMD

CMD 是浏览器端模块化的一种实现方式,最早是由 SeaJS 团队实现的,它通过 define()和 require()来实现模块的加载和使用。
SeaJS 官网

特点

  • 可以按需加载(和 AMD 相比)
    CMD 的依赖仅在需要使用时主动声明才会被加载

代码

目录结构:

  • index.html
  • js
    • main.js
    • sea.js
    • lib
      • module1.js
      • module2.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>

<body>
<script src="js/sea.js"></script>
<script>
// seajs 的简单配置
seajs.config({
base: "./js/lib",
// 配置一些模块引用别名
// alias: {
// "module3": "a/b/c/module3.long.name.min", //实际路径为js/lib/a/b/c/module3.long.name.min.js
// }
});

// 加载入口模块
seajs.use("./js/main");
</script>
</body>
</html>
1
2
3
4
5
6
7
// main.js
define(function (require, exports, module) {
let module1 = require("module1");
console.log(module1.name1);
module1.introduce1();
module1.myFriend();
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// module1.js
define(function (require, exports, module) {
let name1 = "模块1";
function introduce1() {
console.log(`我是${name1}`);
}
var module2 = require("module2");
function myFriend() {
console.log(`我是${name1},我的朋友是${module2.name2}`);
}
module.exports = {
name1,
introduce1,
myFriend,
};
});
1
2
3
4
5
6
7
8
9
10
11
// module2.js
define(function (require, exports, module) {
let name2 = "模块2";
function introduce2() {
console.log(`我是${name2}`);
}
module.exports = {
name2,
introduce2,
};
});

ES6 Module(ESM)

ES6 Module 是浏览器端模块化的一种实现方式,是在语言层面上的实现的模块化标准,它通过 import 和 export 来实现模块的加载和使用。

特点

  • 不能条件加载
    ES6 的模块不是对象,import 命令会被 JavaScript 引擎静态分析,在编译时就引入模块代码,而不是在代码运行时加载,所以无法实现条件加载。也正因为这个,使得静态分析成为可能。
  • 兼容性问题
    目前谷歌浏览器已原生支持 ESM,但是考虑到不同用户的浏览器版本参差不齐,所以兼容性还是要考虑的首要问题
  • 需要服务器环境
    通过文件协议访问网页不能使用 ESM

代码

目录结构:

  • public
    • index.html
    • main.js
    • module1.js
    • module2.js
  • node_modules
    • http-server:因为 ESM 需要部署在服务器,所以使用该第三方库来开启服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>

<body>
<!-- ESM模块的代码必须声明type="module",否则会报错 -->
<script type="module">
import "./main.js"; // 不引入模块的导入方式,被导入的脚本自动执行
</script>
</body>
</html>
1
2
3
4
5
6
7
8
9
// main.js
import { name1 as newName, introduce1 } from "./module1.js"; // 通过 export 方式导出的模块可以通过解构方式导入,并且支持别名
import module2 from "./module2.js"; // 通过 export default 方式导出的模块只能通过这种方式导入

console.log(newName);
introduce1();

console.log(module2.name2);
module2.introduce2();
1
2
3
4
5
6
7
// module1.js
export let name1 = "模块1"; // 通过 export 方式导出

// 通过 export 方式导出
export function introduce1() {
console.log(`我是${name1}`);
}
1
2
3
4
5
6
7
8
9
10
11
// module2.js
let name2 = "模块2";
function introduce2() {
console.log(`我是${name2}`);
}

// 通过 export default 方式导出
export default {
name2,
introduce2,
};

UMD

UMD(Universal Module Definition)并非是一个实现模块化的第三方库,
它只是一种用来编写 javascrit 库的通用模式,该模式会自动判断当前环境应该使用什么模块化标准,
比如 JQuery 就使用了 UMD 模式,所以它在 CommonJS、AMD、CMD、ES6 Module 模块化标准中都可以使用

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/*
这个示例中,一个自调用函数将整个模块包裹起来,并判断当前环境的模块化标准,
如果是AMD标准,则使用define定义模块并返回,
如果是CommonJS标准,则使用module.exports返回模块
否则将模块挂载到root(window)对象上
*/

(function (root, factory) {
if (typeof define === "function" && define.amd) {
// AMD
define(["exports"], factory);
} else if (typeof exports === "object") {
// Node, CommonJS-like
module.exports = factory();
} else {
// Browser globals (root is window)
root.MyModule = factory();
}
})(this, function () {
// 模块的实际代码
var MyModule = {};

MyModule.someFunction = function () {
// 实现一些功能
};

return MyModule;
});

参考

  • 标题: 前端模块化理解
  • 作者: 木头的喵喵拖孩
  • 创建于: 2024-02-26 14:40:26
  • 更新于: 2024-05-21 10:56:15
  • 链接: https://blog.xx-xx.top/2024/02/26/前端模块化理解/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。