JavaScript 模块化完全指南
1. 模块化的演进之路
1.1 为什么需要模块化?
想象你在建造一座大楼:
-
🏗️ 没有模块化:所有材料堆在一起,工人随意取用
// 全局变量满天飞 var userName = "张三"; var userAge = 18; function getUserInfo() { /*...*/ } function updateUserInfo() { /*...*/ } // 1000个函数和变量... -
🏢 有了模块化:材料分类存放,按需使用
// user.js - 用户模块 export const userService = { getUserInfo() { /*...*/ }, updateUserInfo() { /*...*/ } }; // order.js - 订单模块 import { userService } from './user.js';
1.2 模块化解决的问题
| 问题 | 没有模块化 | 有了模块化 |
|---|---|---|
| 命名冲突 | window.name 被覆盖 | 模块作用域隔离 |
| 依赖管理 | 手动排序 script 标签 | 自动依赖加载 |
| 代码组织 | 一个文件数千行 | 按功能拆分模块 |
| 代码复用 | 复制粘贴代码 | 导入导出模块 |
2. 模块化规范的进化史
2.1 原始时代 - 全局变量(2000-2009)
就像原始人用石头打猎:简单但危险
// jQuery 时代的经典写法
(function($) {
// jQuery 插件代码
$.fn.myPlugin = function() { /*...*/ };
})(jQuery);
2.2 CommonJS - Node.js 的选择(2009)
像搭积木一样组织代码:
// math.js
exports.add = (a, b) => a + b;
// main.js
const { add } = require('./math');
console.log(add(2, 3)); // 5
特点:
- ✅ 同步加载,适合服务器
- ❌ 不适合浏览器(需要等待)
2.3 AMD - 异步模块定义(2011)
就像网上购物:先下单,到货再通知你
// 经典案例:RequireJS
define(['jquery', 'lodash'], function($, _) {
return {
init: function() {
$('.btn').on('click', _.debounce(function() {
// 处理点击事件
}, 300));
}
};
});
2.4 UMD - 通用模块定义(2011)
像瑞士军刀:一个模块,到处运行
// 著名库 Lodash 的包装方式
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['jquery'], factory);
} else if (typeof exports === 'object') {
// CommonJS
module.exports = factory(require('jquery'));
} else {
// 浏览器全局变量
root.myLib = factory(root.jQuery);
}
}(this, function($) {
// 实际的模块代码
return {
// 公共API
};
}));
2.5 ES Modules - 现代标准(2015+)
像现代化的城市:规范、高效、可扩展
// 静态导入 - 编译时确定依赖
import React, { useState } from 'react';
// 动态导入 - 按需加载
const loadChart = async () => {
const { Chart } = await import('echarts');
// 使用图表
};
3. 真实项目中的模块化最佳实践
3.1 React 项目的模块组织
// 👎 不好的实践
// BigComponent.js - 一个文件包含所有逻辑
export default function BigComponent() {
// 数百行代码...
}
// 👍 好的实践
// components/User/index.js
export { default } from './User';
// components/User/User.js
export default function User() { /*...*/ }
// components/User/useUser.js
export function useUser() { /*...*/ }
// components/User/styles.js
export const styles = { /*...*/ }
3.2 Vue 项目的模块化
// 👎 不好的实践 - 所有功能写在一个组件里
export default {
data() { /*...*/ },
methods: { /* 几十个方法 */ },
// 几百行代码...
}
// 👍 好的实践 - 按功能拆分
// useUserState.js
export function useUserState() { /*...*/ }
// useUserActions.js
export function useUserActions() { /*...*/ }
// UserList.vue
import { useUserState, useUserActions } from './composables';
4. 模块化工具链
4.1 开发环境
-
Vite: 闪电般的启动速度
npm create vite@latest my-app -- --template react-ts -
Webpack: 功能全面的打包工具
// webpack.config.js module.exports = { entry: './src/index.js', output: { filename: '[name].[contenthash].js', chunkFilename: '[name].[contenthash].js' } };
4.2 生产环境优化
-
代码分割: 按需加载
// React.lazy 示例 const UserDashboard = React.lazy(() => import('./UserDashboard') ); -
Tree Shaking: 清除死代码
// 👎 不利于 Tree Shaking export default { helper1() { /*...*/ }, helper2() { /*...*/ } }; // 👍 有利于 Tree Shaking export function helper1() { /*...*/ } export function helper2() { /*...*/ }
5. 未来趋势
5.1 原生 ESM
<!-- 现代浏览器已支持 -->
<script type="module">
import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
</script>
5.2 Import Maps
<!-- 更优雅的依赖管理 -->
<script type="importmap">
{
"imports": {
"vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js",
"@/utils/": "/src/utils/"
}
}
</script>
5.3 Package Exports
{
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
},
"./package.json": "./package.json"
}
}
6. 总结与建议
选择建议
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 新项目 | ES Modules | 现代标准,工具支持好 |
| 库开发 | UMD + ESM | 兼容性好,支持 Tree Shaking |
| Node.js | ESM | 未来趋势,性能更好 |
最佳实践清单
- ✅ 使用 ES Modules 语法
- ✅ 小而专注的模块
- ✅ 明确的模块边界
- ✅ 避免循环依赖
- ✅ 合理的代码分割
- ✅ 优化加载性能