第10章:模块(Modules)
一、导读
1.1 本章学习目标
第10章介绍JavaScript的模块化开发。本章核心目标:
- 掌握ES6模块:import和export
- 理解CommonJS模块:require和module.exports
- 学会动态导入:import()
- 了解模块打包工具:Webpack、Rollup、Vite
1.2 为什么本章如此重要
模块化是组织大型代码的关键。ES6模块是JavaScript的官方标准,提供了静态分析、Tree Shaking等优势。理解模块系统是现代JavaScript开发的基础。
1.3 核心问题引导
ES6模块和CommonJS有什么本质区别?
命名导出和默认导出有什么区别?
动态导入在什么场景下使用?
模块的循环依赖如何处理?
二、核心概念详解
10.1 ES6模块
javascript
// math.js - 导出
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
export const PI = 3.14159;
export default class Calculator {
constructor() {
this.history = [];
}
}
// main.js - 导入
import Calculator, { add, subtract, PI } from './math.js';
console.log(add(2, 3)); // 5
console.log(PI); // 3.14159
// 重命名导入
import { add as plus } from './math.js';
// 导入所有
import * as math from './math.js';
console.log(math.add(2, 3));10.2 CommonJS模块
javascript
// utils.js - CommonJS导出
function greet(name) {
return `Hello, ${name}!`;
}
function farewell(name) {
return `Goodbye, ${name}!`;
}
module.exports = { greet, farewell };
// 或者
// exports.greet = greet;
// exports.farewell = farewell;
// app.js - CommonJS导入
const { greet, farewell } = require('./utils');
console.log(greet("Alice"));
console.log(farewell("Bob"));10.3 动态导入
javascript
// 按需加载
async function loadModule() {
const module = await import('./math.js');
console.log(module.add(2, 3));
}
// 条件导入
if (needsFeature) {
import('./feature.js').then(module => {
module.init();
});
}
// 路由级别的代码分割
const routes = {
'/dashboard': () => import('./Dashboard.js'),
'/settings': () => import('./Settings.js'),
};
async function navigate(path) {
const loader = routes[path];
if (loader) {
const module = await loader();
module.render();
}
}10.4 模块最佳实践
javascript
// 1. 单一职责:每个模块只做一件事
// utils/validation.js
export function isValidEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
export function isValidPassword(password) {
return password.length >= 8;
}
// 2. 明确导出:使用命名导出而非默认导出
// 推荐
export class UserService { }
export class AuthService { }
// 3. 避免副作用
// 模块顶层代码不应该有副作用
// 副作用应该封装在函数中
// 4. 使用barrel文件
// api/index.js
export { UserService } from './UserService.js';
export { AuthService } from './AuthService.js';
export { DataService } from './DataService.js';
// 使用
import { UserService, AuthService } from './api/index.js';三、实战案例
3.1 模块化应用架构
javascript
// models/User.js
export class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
}
// services/UserService.js
import { User } from '../models/User.js';
export class UserService {
#users = [];
createUser(name, email) {
const user = new User(name, email);
this.#users.push(user);
return user;
}
findUser(email) {
return this.#users.find(u => u.email === email);
}
}
// controllers/UserController.js
import { UserService } from '../services/UserService.js';
export class UserController {
#userService = new UserService();
async register(req) {
const { name, email } = req.body;
const user = this.#userService.createUser(name, email);
return { status: 201, data: user };
}
}
// app.js
import { UserController } from './controllers/UserController.js';
const controller = new UserController();
const result = await controller.register({
body: { name: "Alice", email: "alice@example.com" }
});
console.log(result);四、本章小结
第10章介绍了JavaScript模块化:ES6模块(import/export)、CommonJS(require/module.exports)、动态导入(import())。ES6模块是现代JavaScript的标准。
五、思考与练习
思考题
ES6模块是静态的还是动态的?这和CommonJS有什么区别?
Tree Shaking是什么?为什么ES6模块支持而CommonJS不支持?
模块的循环依赖会产生什么问题?
浏览器原生支持ES6模块吗?需要什么配置?
编程练习
将一个大型脚本重构为模块化结构。
实现一个插件系统(使用动态导入)。
使用模块实现一个简单的依赖注入容器。