Day 27: 模块化开发
📅 日期: 2026年03月19日
🎯 学习目标: 掌握 ES6 模块化,学会组织和管理大型项目代码
⏱️ 预计用时: 2-3 小时
📂 分类: 10-现代JavaScript
🎯 学习目标
通过今天的学习,你将:
- 理解模块化思想:为什么需要模块化
- 掌握 ES6 模块语法:export、import、default
- 学会代码组织:文件结构、命名规范
- 实践依赖管理:npm、yarn、package.json
- 应用实战场景:构建可维护的大型项目
💡 核心概念
1. 什么是模块化
模块化是将代码拆分成独立、可复用的单元(模块),每个模块有自己的作用域,只暴露必要的接口。
// ❌ 没有模块化:全局变量污染
var name = "张三";
var age = 25;
// 多个脚本文件可能互相覆盖
// 变量名冲突、依赖混乱
// ✅ 模块化:独立作用域
// user.js
export const name = "张三";
export const age = 25;
// main.js
import { name, age } from './user.js';
2. 为什么需要模块化
模块化的好处:
- 避免命名冲突:每个模块有独立作用域
- 提高代码复用:模块可以在多处引用
- 便于维护:职责清晰、易于定位问题
- 按需加载:浏览器只加载需要的模块
- 更好的协作:团队成员并行开发不同模块
3. ES6 模块 vs CommonJS
| 特性 | ES6 模块 | CommonJS |
|---|---|---|
| 语法 | export / import |
module.exports / require |
| 加载方式 | 编译时加载 | 运行时加载 |
| 输出 | 值的引用 | 值的拷贝 |
| 使用场景 | 浏览器 + Node.js | 主要 Node.js |
| 支持 | 原生支持 | 需要构建工具 |
🛠️ 语法详解
1. export – 导出
命名导出(Named Exports)
// utils.js
// 导出变量
export const PI = 3.14159;
// 导出函数
export function add(a, b) {
return a + b;
}
// 导出类
export class Calculator {
constructor() {
this.result = 0;
}
add(num) {
this.result += num;
return this;
}
}
// 也可以先定义后导出
const name = "工具模块";
function greet() {
console.log("Hello!");
}
export { name, greet };
// 导出时重命名
export { add as sum, greet as hello };
默认导出(Default Export)
// calculator.js
// 每个模块只能有一个默认导出
export default class Calculator {
constructor() {
this.result = 0;
}
add(num) {
this.result += num;
return this;
}
}
// 或者
class Calculator {
// ...
}
export default Calculator;
// 导出表达式
export default function(a, b) {
return a + b;
}
混合导出
// 同时使用命名导出和默认导出
export default class Calculator {
// ...
}
export const VERSION = "1.0.0";
export function help() {
console.log("使用帮助...");
}
2. import – 导入
导入命名导出
// main.js
// 导入特定的导出
import { PI, add, Calculator } from './utils.js';
// 导入并重命名
import { add as sum, name as moduleName } from './utils.js';
// 导入所有导出(绑定到对象)
import * as utils from './utils.js';
console.log(utils.PI);
console.log(utils.add(1, 2));
导入默认导出
// main.js
// 导入默认导出(不需要大括号)
import Calculator from './calculator.js';
const calc = new Calculator();
calc.add(5).add(3);
console.log(calc.result);
混合导入
// 同时导入默认导出和命名导出
import Calculator, { VERSION, help } from './calculator.js';
只导入副作用
// 有些模块只执行副作用,不导出任何内容
import './polyfill.js';
import './styles.css';
3. 动态导入(Dynamic Import)
// 运行时动态加载模块
async function loadModule() {
const { add } = await import('./utils.js');
console.log(add(1, 2));
}
// 按需加载
button.addEventListener('click', async () => {
const module = await import('./heavy-module.js');
module.doSomething();
});
🎮 实战示例
示例 1:工具模块
// utils/string.js
// 字符串工具函数
export function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
export function truncate(str, length) {
return str.length > length
? str.slice(0, length) + '...'
: str;
}
export function slugify(str) {
return str
.toLowerCase()
.trim()
.replace(/\s+/g, '-')
.replace(/[^\w-]+/g, '');
}
// utils/date.js
// 日期工具函数
export function formatDate(date, format = 'YYYY-MM-DD') {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return format
.replace('YYYY', year)
.replace('MM', month)
.replace('DD', day);
}
export function addDays(date, days) {
const result = new Date(date);
result.setDate(result.getDate() + days);
return result;
}
// utils/index.js
// 统一导出所有工具模块
export * from './string.js';
export * from './date.js';
示例 2:API 模块
// api/config.js
// API 配置
export const BASE_URL = 'https://api.example.com';
export const TIMEOUT = 5000;
export const HEADERS = {
'Content-Type': 'application/json',
};
// api/request.js
// HTTP 请求封装
import { BASE_URL, TIMEOUT, HEADERS } from './config.js';
export async function request(endpoint, options = {}) {
const url = `${BASE_URL}${endpoint}`;
const config = {
...options,
headers: {
...HEADERS,
...options.headers,
},
};
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), TIMEOUT);
try {
const response = await fetch(url, {
...config,
signal: controller.signal,
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
clearTimeout(timeoutId);
throw error;
}
}
// api/user.js
// 用户相关 API
import { request } from './request.js';
export async function getUser(id) {
return await request(`/users/${id}`);
}
export async function createUser(data) {
return await request('/users', {
method: 'POST',
body: JSON.stringify(data),
});
}
export async function updateUser(id, data) {
return await request(`/users/${id}`, {
method: 'PUT',
body: JSON.stringify(data),
});
}
export async function deleteUser(id) {
return await request(`/users/${id}`, {
method: 'DELETE',
});
}
示例 3:组件模块
// components/Button.js
// 按钮组件
import './styles/button.css';
export class Button {
constructor({ text, onClick, variant = 'primary' }) {
this.text = text;
this.onClick = onClick;
this.variant = variant;
this.element = this.render();
}
render() {
const button = document.createElement('button');
button.className = `btn btn-${this.variant}`;
button.textContent = this.text;
button.addEventListener('click', this.onClick);
return button;
}
mount(parent) {
parent.appendChild(this.element);
}
unmount() {
this.element.remove();
}
}
// components/Modal.js
// 模态框组件
export class Modal {
constructor({ title, content, onClose }) {
this.title = title;
this.content = content;
this.onClose = onClose;
this.element = this.render();
}
render() {
const modal = document.createElement('div');
modal.className = 'modal';
modal.innerHTML = `
<div class="modal-overlay">
<div class="modal-content">
<div class="modal-header">
<h3>${this.title}</h3>
<button class="modal-close">×</button>
</div>
<div class="modal-body">
${this.content}
</div>
</div>
</div>
`;
const closeBtn = modal.querySelector('.modal-close');
closeBtn.addEventListener('click', () => this.close());
return modal;
}
open() {
document.body.appendChild(this.element);
}
close() {
this.element.remove();
if (this.onClose) this.onClose();
}
}
📂 项目结构
推荐的文件组织方式
project/
├── index.html # 入口 HTML
├── main.js # 入口 JS
├── package.json # 项目配置
├── src/
│ ├── api/ # API 模块
│ │ ├── config.js
│ │ ├── request.js
│ │ └── user.js
│ ├── components/ # 组件模块
│ │ ├── Button.js
│ │ ├── Modal.js
│ │ └── index.js # 统一导出
│ ├── utils/ # 工具模块
│ │ ├── string.js
│ │ ├── date.js
│ │ └── index.js
│ ├── styles/ # 样式文件
│ │ └── main.css
│ └── assets/ # 静态资源
│ ├── images/
│ └── fonts/
└── public/ # 公共资源
└── favicon.ico
package.json 配置
{
"name": "my-project",
"version": "1.0.0",
"type": "module",
"main": "main.js",
"scripts": {
"start": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"axios": "^1.6.0"
},
"devDependencies": {
"vite": "^5.0.0"
}
}
注意:"type": "module" 告诉 Node.js 使用 ES6 模块。
⚠️ 重要注意事项
1. 模块的加载顺序
// ✅ 正确:先导入后使用
import { utils } from './utils.js';
console.log(utils.formatDate(new Date()));
// ❌ 错误:在导入前使用
console.log(utils.formatDate(new Date())); // ReferenceError
import { utils } from './utils.js';
2. 循环依赖
// a.js
import { foo } from './b.js';
export const bar = 'bar';
// b.js
import { bar } from './a.js';
export const foo = 'foo';
// ⚠️ 可能导致 undefined
// 解决方案:重构代码,避免循环依赖
3. this 的指向
// ES6 模块中,顶级 this 是 undefined
console.log(this); // undefined
// 而不是全局对象(window)
4. import 的提升
// import 会被自动提升到文件顶部
func(); // 可以正常工作
import { func } from './utils.js';
// 但为了可读性,建议把 import 放在文件开头
🎓 实战应用场景
场景 1: 大型 Web 应用
// 应用入口
import { Router } from './router.js';
import { Store } from './store.js';
import { App } from './App.js';
const app = new App({
router: new Router(),
store: new Store(),
});
app.mount('#app');
场景 2: 工具库开发
// my-utils/index.js
export * from './string.js';
export * from './array.js';
export * from './object.js';
export * from './date.js';
// 用户使用
import { capitalize, formatDate, debounce } from 'my-utils';
场景 3: 组件库
// my-ui/index.js
export { Button } from './Button.js';
export { Modal } from './Modal.js';
export { Input } from './Input.js';
export { default as Card } from './Card.js';
// 用户使用
import { Button, Modal, Input, Card } from 'my-ui';
✍️ 练习任务
基础练习(必做)
-
导出导入:
- [ ] 创建一个工具模块并导出函数
- [ ] 使用命名导出和默认导出
- [ ] 在另一个模块中导入使用
-
项目结构:
- [ ] 按功能组织模块文件
- [ ] 创建 index.js 统一导出
- [ ] 配置 package.json
-
动态导入:
- [ ] 实现按需加载模块
- [ ] 处理加载错误
- [ ] 显示加载状态
进阶练习(推荐)
-
模块设计:
- 设计一个可复用的表单验证模块
- 设计一个 HTTP 请求封装模块
- 设计一个本地存储封装模块
-
构建工具:
- 使用 Vite 打包项目
- 配置路径别名(@/src)
- 代码分割和懒加载
-
最佳实践:
- 命名规范
- 目录组织
- 依赖管理
实战挑战(选做)
-
构建组件库:
- 开发一套 UI 组件
- 发布到 npm
- 编写文档和示例
-
微前端:
- 模块联邦
- 运行时集成
- 独立部署
-
Monorepo:
- pnpm workspace
- Turborepo
- 多项目管理
💡 常见问题 FAQ
Q1: ES6 模块和 CommonJS 可以混用吗?
A:
- Node.js 中可以混用,但需要正确处理
- 使用
.mjs扩展名表示 ES6 模块 - 使用
.cjs扩展名表示 CommonJS
Q2: 如何在浏览器中使用 ES6 模块?
A:
<!-- 方式 1:使用 type="module" -->
<script type="module" src="main.js"></script>
<!-- 方式 2:内联模块 -->
<script type="module">
import { func } from './utils.js';
func();
</script>
Q3: 构建工具是必须的吗?
A:
- 开发环境可以使用原生 ES6 模块
- 生产环境建议使用构建工具(Vite、Webpack)
- 构建工具提供优化、兼容性、打包等功能
Q4: 如何处理模块版本?
A:
// package.json
{
"dependencies": {
"lodash": "^4.17.21", // 兼容版本
"axios": "1.6.0", // 精确版本
"react": "~18.2.0" // 补丁版本
}
}
📚 拓展阅读
官方文档
推荐资源
构建工具
🎉 总结
今天我们深入学习了 ES6 模块化开发,涵盖了:
✅ 模块化思想:为什么需要模块化
✅ ES6 语法:export、import、default
✅ 代码组织:文件结构、命名规范
✅ 依赖管理:npm、yarn、package.json
✅ 实战应用:构建可维护的大型项目
模块化是现代 JavaScript 开发的基础,掌握它能让你写出更清晰、更易维护的代码。
下一节课预告: Day 28 将学习 构建工具与工程化,学习如何使用 Vite 打包项目!
💪 加油!坚持就是胜利!