前言
当项目代码越来越多时,如果所有变量、函数、对象都写在一个文件中,就会带来很多问题:
- 容易命名冲突
- 不方便协作
- 代码难维护
- 功能难复用
所以 JavaScript 需要 模块化。
模块化并不只是“把代码拆分成多个文件”,更重要的是:
让每个文件都有自己的作用域,并且可以通过导入和导出组织代码。
本文就来系统讲清楚 JavaScript 模块化的发展和常见方案。
一、为什么需要模块化?
早期 JavaScript 没有模块系统,大家通常把变量和函数直接写到全局作用域中:
var name = 'Tom';
function getUser() {
return name;
}
如果多个文件都写同名变量,就容易冲突。
模块化的出现,就是为了解决这些问题:
- 避免全局污染
- 提高代码复用
- 方便拆分文件
- 便于团队协作
- 提高可维护性
二、早期模块化:IIFE
在 ES Module 出现之前,经常用立即执行函数来模拟模块作用域。
const userModule = (function () {
let name = 'Tom';
function getName() {
return name;
}
function setName(newName) {
name = newName;
}
return {
getName,
setName
};
})();
console.log(userModule.getName()); // Tom
这种方式本质上利用了 闭包。
特点:
- 可以创建私有作用域
- 能避免部分全局污染
- 但不够标准,维护成本高
三、CommonJS
CommonJS 是 Node.js 中常见的模块化规范。
导出
// math.js
function add(a, b) {
return a + b;
}
module.exports = {
add
};
导入
// app.js
const math = require('./math');
console.log(math.add(1, 2)); // 3
特点:
-
主要用于 Node.js
-
使用
require导入
-
使用
module.exports导出
-
同步加载
四、ES Module(ESM)
这是现在最主流、最推荐的模块化方案。
命名导出
// math.js
export function add(a, b) {
return a + b;
}
export function sub(a, b) {
return a - b;
}
导入:
import { add, sub } from './math.js';
console.log(add(1, 2));
console.log(sub(5, 3));
默认导出
// user.js
export default function getUser() {
return { name: 'Tom' };
}
导入:
import getUser from './user.js';
console.log(getUser());
五、命名导出和默认导出的区别
命名导出
export const name = 'Tom';
导入时必须使用对应名字:
import { name } from './file.js';
默认导出
export default function () {}
导入时名字可以自定义:
import myFn from './file.js';
六、CommonJS 和 ES Module 的区别
CommonJS
const math = require('./math');
module.exports = { add };
ES Module
import { add } from './math.js';
export { add };
常见区别:
- CommonJS 多用于 Node.js 传统环境
- ES Module 是 JavaScript 官方标准模块系统
- CommonJS 是同步加载
- ES Module 更适合现代前端工程化
七、模块化的实际意义
模块化最大的价值是让代码更清晰。
比如一个项目可以拆成:
src
├── api
├── utils
├── components
├── views
└── store
每个模块负责不同功能,代码更容易维护。
八、总结
JavaScript 模块化的核心目标是:
把代码拆分成独立、可维护、可复用的模块。
学习模块化时,重点记住:
-
早期有 IIFE
-
Node.js 常见 CommonJS
-
现代前端主流是 ES Module
-
export / import是最常见写法