模块化的发展
// math.js - 导出单个变量
export const PI = 3.14159;
export const E = 2.71828;
// 导出函数
export function sum(a, b) {
return a + b;
}
// 导出类
export class Calculator {
multiply(a, b) {
return a * b;
}
}
// 默认导出(每个模块只能有一个)
const defaultExport = '这是默认导出';
export default defaultExport;
// 也可以直接导出
export default function() {
console.log('默认函数');
};
// 导入命名导出
import { PI, sum, Calculator } from "./math.js";
import { PI as piValue } from "./math.js"; // 重命名
// 导入默认导出
import myDefault from "./math.js";
import myDefault, { PI } from "./math.js"; // 混合导入
// 导入所有导出
import * as MathModule from "./math.js";
console.log(MathModule.PI);
console.log(MathModule.sum(1, 2));
// 仅执行模块,不导入任何内容
import "./init.js"; // 用于执行副作用
// 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;
}
// utils/number.js
export function formatNumber(num) {
return num.toLocaleString();
}
// utils/index.js - 聚合导出
// 方式1:重新导出单个
export { capitalize } from "./string.js";
export { formatNumber } from "./number.js";
// 方式2:重新导出所有
export * from "./string.js";
export * from "./number.js";
// 方式3:重命名后导出
export { capitalize as cap } from "./string.js";
// 主文件导入
import { capitalize, formatNumber } from "./utils/index.js";
// 动态导入返回 Promise
const modulePath = "./math.js";
// 按需加载模块
button.addEventListener("click", async () => {
try {
const math = await import(modulePath);
console.log(math.PI);
console.log(math.sum(1, 2));
} catch (error) {
console.error("模块加载失败:", error);
}
});
// 条件导入
async function loadModule(condition) {
if (condition) {
const { heavyFunction } = await import("./heavy-module.js");
heavyFunction();
} else {
const { lightFunction } = await import("./light-module.js");
lightFunction();
}
}
// 多个动态导入
async function loadMultipleModules() {
const [module1, module2, module3] = await Promise.all([
import("./module1.js"),
import("./module2.js"),
import("./module3.js"),
]);
// 使用模块
module1.doSomething();
module2.doSomethingElse();
}
// config.js
const config = {
api: {
baseURL: process.env.API_BASE_URL || "https://api.example.com",
timeout: 5000,
retries: 3,
},
app: {
name: "MyApp",
version: "1.0.0",
debug: process.env.NODE_ENV === "development",
},
features: {
darkMode: true,
notifications: false,
analytics: true,
},
};
// 冻结配置防止修改
Object.freeze(config);
export default config;
// 使用
import config from "./config.js";
console.log(config.api.baseURL);
// utils/helpers.js
// 防抖函数
export function debounce(func, delay) {
let timeoutId;
return function (...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
// 节流函数
export function throttle(func, limit) {
let inThrottle;
return function (...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
}
// 深度克隆
export function deepClone(obj) {
if (obj === null || typeof obj !== "object") return obj;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof Array) return obj.map(item => deepClone(item));
if (typeof obj === "object") {
const cloned = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepClone(obj[key]);
}
}
return cloned;
}
}
// 表单验证
export const validators = {
isEmail: email => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email),
isPhone: phone => /^1[3-9]\d{9}$/.test(phone),
isRequired: value => value != null && value.toString().trim() !== "",
};
// models/User.js
class User {
constructor(id, name, email) {
this.id = id;
this.name = name;
this.email = email;
this.createdAt = new Date();
}
get displayName() {
return `${this.name} (${this.email})`;
}
validate() {
const errors = [];
if (!this.name || this.name.trim() === "") {
errors.push("姓名不能为空");
}
if (!this.email || !this.email.includes("@")) {
errors.push("邮箱格式不正确");
}
return errors;
}
toJSON() {
return {
id: this.id,
name: this.name,
email: this.email,
createdAt: this.createdAt.toISOString(),
};
}
}
export default User;
// 使用
import User from "./models/User.js";
const user = new User(1, "张三", "zhangsan@example.com");
console.log(user.displayName);
// services/api.js
const BASE_URL = "https://api.example.com";
class ApiError extends Error {
constructor(message, status) {
super(message);
this.status = status;
this.name = "ApiError";
}
}
export class ApiService {
constructor(token = null) {
this.token = token;
}
async request(endpoint, options = {}) {
const url = `${BASE_URL}${endpoint}`;
const headers = {
"Content-Type": "application/json",
...options.headers,
};
if (this.token) {
headers["Authorization"] = `Bearer ${this.token}`;
}
const config = {
...options,
headers,
};
try {
const response = await fetch(url, config);
if (!response.ok) {
throw new ApiError(`HTTP错误 ${response.status}`, response.status);
}
const data = await response.json();
return data;
} catch (error) {
console.error("API请求失败:", error);
throw error;
}
}
// 用户相关
async login(credentials) {
return this.request("/auth/login", {
method: "POST",
body: JSON.stringify(credentials),
});
}
async getUser(id) {
return this.request(`/users/${id}`);
}
async updateUser(id, data) {
return this.request(`/users/${id}`, {
method: "PUT",
body: JSON.stringify(data),
});
}
// 商品相关
async getProducts(params = {}) {
const query = new URLSearchParams(params).toString();
return this.request(`/products?${query}`);
}
}
// 创建单例实例
export const api = new ApiService();
// components/Modal.js
import { createElement, addClass, removeClass } from "./dom-helpers.js";
export class Modal {
constructor(options = {}) {
this.options = {
title: "提示",
content: "",
showClose: true,
...options,
};
this.isOpen = false;
this.modalElement = null;
}
create() {
const modal = createElement("div", "modal");
const backdrop = createElement("div", "modal-backdrop");
const dialog = createElement("div", "modal-dialog");
// 标题
if (this.options.title) {
const header = createElement("div", "modal-header");
const title = createElement("h3", "modal-title", this.options.title);
header.appendChild(title);
if (this.options.showClose) {
const closeBtn = createElement("button", "modal-close", "×");
closeBtn.addEventListener("click", () => this.close());
header.appendChild(closeBtn);
}
dialog.appendChild(header);
}
// 内容
const body = createElement("div", "modal-body");
body.innerHTML = this.options.content;
dialog.appendChild(body);
modal.appendChild(backdrop);
modal.appendChild(dialog);
backdrop.addEventListener("click", () => this.close());
this.modalElement = modal;
return modal;
}
open() {
if (!this.modalElement) {
this.create();
}
document.body.appendChild(this.modalElement);
addClass(document.body, "modal-open");
this.isOpen = true;
// 触发事件
this.onOpen?.();
}
close() {
if (this.modalElement && this.modalElement.parentNode) {
this.modalElement.parentNode.removeChild(this.modalElement);
removeClass(document.body, "modal-open");
this.isOpen = false;
// 触发事件
this.onClose?.();
}
}
setContent(content) {
if (this.modalElement) {
const body = this.modalElement.querySelector(".modal-body");
if (body) {
body.innerHTML = content;
}
}
}
}
// 使用
import { Modal } from "./components/Modal.js";
const modal = new Modal({
title: "确认删除",
content: "确定要删除这项内容吗?",
});
modal.onOpen = () => console.log("模态框已打开");
modal.onClose = () => console.log("模态框已关闭");
modal.open();
src/
├── index.js # 应用入口
├── config/ # 配置文件
│ ├── constants.js # 常量
│ ├── settings.js # 设置
│ └── index.js # 配置聚合
├── utils/ # 工具函数
│ ├── helpers.js # 辅助函数
│ ├── validators.js # 验证函数
│ ├── formatters.js # 格式化函数
│ └── index.js # 工具聚合
├── services/ # 服务层
│ ├── api.js # API 服务
│ ├── auth.js # 认证服务
│ └── storage.js # 存储服务
├── models/ # 数据模型
│ ├── User.js # 用户模型
│ ├── Product.js # 商品模型
│ └── Order.js # 订单模型
├── components/ # UI 组件
│ ├── Modal/
│ │ ├── Modal.js
│ │ ├── Modal.css
│ │ └── index.js
│ └── Button/
│ ├── Button.js
│ ├── Button.css
│ └── index.js
└── store/ # 状态管理
├── userStore.js # 用户状态
├── cartStore.js # 购物车状态
└── index.js # store 聚合
// 原则1:单一职责
// 每个模块只做一件事,并且做好
// 原则2:明确导出
// 清楚哪些是公开API,哪些是内部实现
// 原则3:避免循环依赖
// 模块间依赖应该是单向的
// 原则4:合理命名
// 使用清晰、描述性的名称
// 原则5:文档化
// 使用 JSDoc 注释
/**
* 计算两个数的和
* @param {number} a - 第一个数
* @param {number} b - 第二个数
* @returns {number} 两数之和
* @example
* const result = sum(1, 2); // 3
*/
export function sum(a, b) {
return a + b;
}
{
"name": "my-project",
"version": "1.0.0",
"type": "module", // 使用 ES6 模块
"main": "src/index.js",
"exports": {
".": "./src/index.js",
"./utils": "./src/utils/index.js",
"./components/*": "./src/components/*/index.js"
},
"scripts": {
"build": "rollup -c", // 或 webpack, vite, esbuild 等
"dev": "vite"
}
}
<!-- 直接使用 ES6 模块 -->
<script type="module">
import { sum } from './math.js';
console.log(sum(1, 2));
</script>
<!-- 使用打包后的文件 -->
<script src="dist/bundle.js" type="module"></script>
// 避免方法1:重构代码,提取公共部分
// 避免方法2:使用函数提升
// 避免方法3:动态导入
// moduleA.js
import { funcB } from "./moduleB.js";
export function funcA() {
console.log("A");
funcB();
}
// moduleB.js
export async function funcB() {
// 动态导入避免循环
const { funcA } = await import("./moduleA.js");
console.log("B");
}
// 模块是单例的,只执行一次
// counter.js
let count = 0;
export function increment() {
count++;
return count;
}
// main.js
import { increment } from "./counter.js";
console.log(increment()); // 1
console.log(increment()); // 2
// 另一个文件导入相同模块
import { increment } from "./counter.js";
console.log(increment()); // 3 - 共享同一个count
// utils.js
// 以下函数只有 usedFunction 会被打包
export function usedFunction() {
console.log('被使用了');
}
export function unusedFunction() {
console.log('未被使用');
}
// 有副作用的函数要特殊标记
export function hasSideEffects() {
console.log('有副作用');
}
// package.json 中配置
{
"sideEffects": [
"**/*.css",
"**/*.scss"
]
}