JavaScript 模块化入门:从零理解 ES6 模块系统
本文内容基于 AI 生成结果整理,可能包含不准确信息,仅供参考使用。
一、模块化是什么?为什么重要?
想象你在组装一台电脑:
- 非模块化:所有零件焊死在主板上,无法单独更换
- 模块化:CPU、内存、硬盘都是独立模块,可以轻松升级替换
代码模块化也是同样道理,它带来三大核心优势:
- 可维护性:每个模块专注单一功能,修改不影响其他部分
- 可复用性:写好的模块可以在多个项目中重复使用
- 避免污染:模块有自己的作用域,不会产生变量冲突
二、ES6 模块基础语法
1. 导出模块:三种方式
javascript
// utils.js
// 1. 单独导出(推荐)
export const PI = 3.1415926;
export function double(x) {
return x * 2;
}
// 2. 集中导出
const VERSION = "1.0";
function log(message) {
console.log(message);
}
export { VERSION, log };
// 3. 默认导出(每个文件只能有一个)
export default class Calculator {
add(a, b) {
return a + b;
}
}
2. 导入模块:四种方式
javascript
// app.js
// 1. 导入命名导出
import { PI, double } from "./utils.js";
console.log(PI); // 3.1415926
// 2. 导入默认导出
import Calculator from "./utils.js";
const calc = new Calculator();
// 3. 重命名导入
import { log as logger } from "./utils.js";
// 4. 整体导入
import * as utils from "./utils.js";
console.log(utils.VERSION);
三、实战应用场景
场景 1:组件化开发
javascript
// components/Button.js
export default function Button(text) {
const button = document.createElement("button");
button.textContent = text;
return button;
}
// main.js
import Button from "./components/Button.js";
document.body.appendChild(Button("点击我"));
场景 2:工具库封装
javascript
// lib/math.js
export function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
export function average(...numbers) {
return sum(...numbers) / numbers.length;
}
// app.js
import { sum, average } from "./lib/math.js";
console.log(sum(1, 2, 3)); // 6
四、常见问题解答
Q1:什么时候用默认导出?
最佳实践 :
- 当模块主要导出一个功能时(如 React 组件)
- 当导入方不需要知道具体名称时
javascript
// 默认导出
export default function() { ... }
// 导入时可以任意命名
import myFunction from './module.js'
Q2:循环依赖怎么办?
解决方案 :
重构代码结构,避免循环 使用函数提升特性
javascript
// a.js
import { b } from "./b.js";
export function a() {
return b();
}
// b.js
import { a } from "./a.js";
export function b() {
return a();
} // ❌ 避免这样设计
Q3:浏览器兼容性如何处理?
两种方案 :
使用打包工具(Webpack/Rollup) 动态导入 + 回退方案
javascript
<script type="module" src="app.js"></script>
<script nomodule src="legacy.js"></script>
五、进阶技巧
1. 动态导入(按需加载)
javascript
// 点击按钮时才加载模块
button.addEventListener("click", async () => {
const module = await import("./dialog.js");
module.showDialog("Hello!");
});
2. 复合导出模式
javascript
// 先集中导入再重新导出
export { default as Button } from "./Button.js";
export { Input, Textarea } from "./formElements.js";
3. 配合打包工具优化
javascript
// webpack.config.js
module.exports = {
optimization: {
splitChunks: {
chunks: "all", // 自动拆分公共模块
},
},
};
六、模块化最佳实践
- 单一职责原则:每个模块只做一件事
- 明确依赖关系:避免深层嵌套导入
- 命名一致性:
- 文件名使用 kebab-case(如 my-module.js)
- 导出的变量使用 camelCase
- 文档注释:使用 JSDoc 说明模块用途
javascript
/**
* 计算商品折扣价格
* @param {number} price - 原价
* @param {number} discount - 折扣比例(0-1)
* @returns {number} 折后价格
*/
export function applyDiscount(price, discount) {
return price * (1 - discount);
}
七、从零实现简易模块加载器
理解模块原理(教学示例):
javascript
const moduleCache = {};
function require(moduleName) {
if (moduleCache[moduleName]) {
return moduleCache[moduleName].exports;
}
const module = {
exports: {},
};
// 实际实现中这里会读取文件内容
const code = `
const message = "Hello Module"
module.exports = { message }
`;
// 相当于eval但更安全
Function("module", "exports", code)(module, module.exports);
moduleCache[moduleName] = module;
return module.exports;
}
const myModule = require("example");
console.log(myModule.message); // "Hello Module"