前端工程模块化

为什么需要模块化

当前端工程到达一定规模后,就会出现下面的问题:

  • 全局变量污染:多个文件中定义的全局变量容易冲突
  • 依赖混乱:多个文件之间的依赖关系复杂,难以维护

上面的问题,共同导致了代码文件难以细分,难以维护模块化就是为了解决上面两个问题出现的。
模块化是指把一个大的程序臃肿的代码细分成一个个小的文件,每个文件都是一个模块,通过模块化可以提高代码的可维护性和可读性。可以有效解决项目体量大时全局变量污染以及依赖混乱的问题。

模块化规范

  1. CommonJS CMJ 社区提出的模块化规范,Node.js 遵循这个规范
  2. ES module ECMAScript 6 的模块化规范,浏览器原生支持

node环境

下载地址

CommonJS

CommonJS如何实现模块化

node天生支持CommonJS模块化标准
node规定:

  1. node中的每个js文件都是一个CMJ模块,通过node命令运行的模块,叫做入口模块
  2. 模块中的所有全局定义的变量、函数、都不会污染到其他模块
  3. 模块可以使用module.exports暴露(导出)一些内容给其他模块使用,也可以使用require("模块路径")引入(导入)其他模块的内容(模块路径必须以./../ 开头)
  4. 模块有缓存机制,模块在第一次加载后会被缓存,再次加载时会优先读取缓存使用缓存结果。后续对该模块导入时,不会重新加载,而是直接读取缓存结果。

CommonJS模块化的实现

因为浏览器可以同时运行多个js文件,而node只能运行一个,所以我们会有一个主要启动文件,别的功能通过引入模块的方式实现
入口模块

1
2
3
4
5
6
7
8
9
10
11
// index.js
// 启动文件 通过node命令运行的文件
const math = require("./math") // 引入功能模块 后缀名可省略 返回{isOdd: fn, sum: fn}
console.log(math.isOdd(10)) // false
console.log(math.sum(10, 20)) // 30

// 这里多次导入math.js
require("./math")
require("./math")
require("./math")
require("./math")

功能模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 // math.js 
// 提供一些数学相关的函数
log("math.js被加载了")
function isOdd(num) {
return num % 2 !== 0
}

function sum(a, b) {
return a + b
}

module.exports = { //导出,可以自定义名字,也可以使用对象形式
isOdd, //导出odd函数
sum //导出加法函数
}

运行结果

1
2
3
4
math.js被加载了 
false
30
// 因为缓存机制,实际只导入了一次

ES module

ES module如何实现模块化

ES module是ES6提出的模块化规范,浏览器原生支持,Node.js从13.2版本开始支持
依赖类型:

  • 静态依赖:在编译时就能确定依赖关系
  • 动态依赖:在运行时才能确定依赖关系

ES module的依赖关系在编译时就能确定,所以ES module的依赖关系是静态的,而CommonJS的依赖关系是动态的。

ES module模块化的实现

ES module也是区分入口模块和功能模块的,入口模块通过import导入功能模块,功能模块通过export导出内容。但是ES module的导出有两种方式:

  • 默认导出:使用export default导出,一个模块只能有一个默认导出
  • 具名导出:使用export导出,可以导出多个

一个模块可以有多个具名导出,但是只能有一个默认导出。他们两个可以同时存在。

入口模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// index.js
// 静态导入
// 仅运行一次,不导入任何内容
import "./math.js"
// 导入具名导出
import { isOdd, sum } from "./math.js"
// 导入具名导出 重命名
import { isOdd as odd, sum as add } from "./math.js"
// 导入所有具名导出 并将模块对象放入math对象中(重命名)
import * as math from "./math.js"
// 导入默认导出
import math from "./math.js"
// 导入default,并重命名(必须重命名因为default是关键字)
import { default as math } from "./math.js"

// 动态导入
import("./math.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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// math.js
// 提供一些数学相关的函数
// 具名导出 常用
export const a = 0
// 具名导出 常用
export function sum(a, b) {
return a + b
}
// 函数也可以这样导出
// export const sum = (a, b) => a + b
// 具名导出
const c = 0, d = 1
export { c, d} // 单个导出也要用大括号
// 具名导出 重命名
const k = 0
export { k as temp}

// 默认导出 常用
// export default 0
// 默认导出
// export default function isOdd(num) {
// return num % 2 !== 0
// }
// 具名+默认导出
const f = 0
function isOdd(num) {
return num % 2 !== 0
}
export { f, isOdd as default}

// 以上代码将导出下面对象
{
a: 0,
sum: fn,
c: 0,
d: 1,
temp: 0,
f: 0,
default: fn
}

总结

  • CommonJS主要通过入口模块和功能模块配合,入口模块引入功能模块实现功能
  • ES module 类似结构但语法更灵活,支持更多导入导出方式

以现在来看,目前使用ES module的情况更多,因为浏览器原生支持,而且ES module的依赖关系是静态的,更加方便管理。但是在Node.js中,CommonJS还是很常用的,因为Node.js是CommonJS的天然支持者,而且CommonJS的模块化规范在Node.js中已经被广泛使用,所以在Node.js中使用CommonJS也是很常见的。

作者

小郑

发布于

2025-03-13

更新于

2025-08-01

许可协议

评论