Day-27-模块化开发

Day 27: 模块化开发

📅 日期: 2026年03月19日
🎯 学习目标: 掌握 ES6 模块化,学会组织和管理大型项目代码
⏱️ 预计用时: 2-3 小时
📂 分类: 10-现代JavaScript


🎯 学习目标

通过今天的学习,你将:

  1. 理解模块化思想:为什么需要模块化
  2. 掌握 ES6 模块语法:export、import、default
  3. 学会代码组织:文件结构、命名规范
  4. 实践依赖管理:npm、yarn、package.json
  5. 应用实战场景:构建可维护的大型项目

💡 核心概念

1. 什么是模块化

模块化是将代码拆分成独立、可复用的单元(模块),每个模块有自己的作用域,只暴露必要的接口。

// ❌ 没有模块化:全局变量污染
var name = "张三";
var age = 25;

// 多个脚本文件可能互相覆盖
// 变量名冲突、依赖混乱

// ✅ 模块化:独立作用域
// user.js
export const name = "张三";
export const age = 25;

// main.js
import { name, age } from './user.js';

2. 为什么需要模块化

模块化的好处

  1. 避免命名冲突:每个模块有独立作用域
  2. 提高代码复用:模块可以在多处引用
  3. 便于维护:职责清晰、易于定位问题
  4. 按需加载:浏览器只加载需要的模块
  5. 更好的协作:团队成员并行开发不同模块

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">&times;</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';

✍️ 练习任务

基础练习(必做)

  1. 导出导入

    • [ ] 创建一个工具模块并导出函数
    • [ ] 使用命名导出和默认导出
    • [ ] 在另一个模块中导入使用
  2. 项目结构

    • [ ] 按功能组织模块文件
    • [ ] 创建 index.js 统一导出
    • [ ] 配置 package.json
  3. 动态导入

    • [ ] 实现按需加载模块
    • [ ] 处理加载错误
    • [ ] 显示加载状态

进阶练习(推荐)

  1. 模块设计

    • 设计一个可复用的表单验证模块
    • 设计一个 HTTP 请求封装模块
    • 设计一个本地存储封装模块
  2. 构建工具

    • 使用 Vite 打包项目
    • 配置路径别名(@/src)
    • 代码分割和懒加载
  3. 最佳实践

    • 命名规范
    • 目录组织
    • 依赖管理

实战挑战(选做)

  1. 构建组件库

    • 开发一套 UI 组件
    • 发布到 npm
    • 编写文档和示例
  2. 微前端

    • 模块联邦
    • 运行时集成
    • 独立部署
  3. 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"        // 补丁版本
  }
}

📚 拓展阅读

官方文档

推荐资源

  1. JavaScript Modules
  2. ES6 Modules in Depth

构建工具

  • Vite – 下一代前端构建工具
  • Webpack – 模块打包器
  • Rollup – JavaScript 模块打包器

🎉 总结

今天我们深入学习了 ES6 模块化开发,涵盖了:

模块化思想:为什么需要模块化
ES6 语法:export、import、default
代码组织:文件结构、命名规范
依赖管理:npm、yarn、package.json
实战应用:构建可维护的大型项目

模块化是现代 JavaScript 开发的基础,掌握它能让你写出更清晰、更易维护的代码。


下一节课预告: Day 28 将学习 构建工具与工程化,学习如何使用 Vite 打包项目!

💪 加油!坚持就是胜利!