每天一个高级前端知识 - Day 25
今日主题:前端设计模式 - 构建可维护的大型应用
核心概念:设计模式是解决特定问题的成熟方案,而非教条
在框架林立的今天,设计模式依然是理解复杂系统、构建可维护应用的元技能。
🔬 前端常用设计模式全景图
创建型模式 ── 如何创建对象
├── 工厂模式 (Factory)
├── 单例模式 (Singleton)
├── 建造者模式 (Builder)
└── 原型模式 (Prototype)
结构型模式 ── 如何组织对象
├── 适配器模式 (Adapter)
├── 装饰器模式 (Decorator)
├── 代理模式 (Proxy)
└── 组合模式 (Composite)
行为型模式 ── 对象如何协作
├── 观察者模式 (Observer)
├── 策略模式 (Strategy)
├── 状态模式 (State)
├── 命令模式 (Command)
└── 职责链模式 (Chain of Responsibility)
🏭 创建型模式
// ============ 1. 工厂模式 ============
// 场景:根据不同类型创建不同的 UI 组件
interface Button {
render(): void;
onClick(): void;
}
class PrimaryButton implements Button {
render() { console.log('渲染主要按钮'); }
onClick() { console.log('主要按钮点击'); }
}
class SecondaryButton implements Button {
render() { console.log('渲染次要按钮'); }
onClick() { console.log('次要按钮点击'); }
}
class DangerButton implements Button {
render() { console.log('渲染危险按钮'); }
onClick() { console.log('危险按钮点击'); }
}
// 工厂
class ButtonFactory {
static createButton(type: 'primary' | 'secondary' | 'danger'): Button {
switch (type) {
case 'primary': return new PrimaryButton();
case 'secondary': return new SecondaryButton();
case 'danger': return new DangerButton();
default: throw new Error(`Unknown button type: ${type}`);
}
}
}
// 使用
const button = ButtonFactory.createButton('primary');
button.render();
// ============ 2. 单例模式 ============
// 场景:全局状态管理、日志记录器、数据库连接池
class Store {
private static instance: Store;
private state: Record<string, any> = {};
private constructor() {} // 私有构造函数,禁止外部实例化
static getInstance(): Store {
if (!Store.instance) {
Store.instance = new Store();
}
return Store.instance;
}
set(key: string, value: any) {
this.state[key] = value;
this.notify(key, value);
}
get(key: string) {
return this.state[key];
}
private notify(key: string, value: any) {
console.log(`状态变更: ${key} = ${value}`);
}
}
// 使用
const store1 = Store.getInstance();
const store2 = Store.getInstance();
console.log(store1 === store2); // true
// React 中的单例模式
// 全局 Toast 管理器
class ToastManager {
private static instance: ToastManager;
private toasts: Toast[] = [];
private listeners: Set<() => void> = new Set();
static getInstance() {
if (!ToastManager.instance) {
ToastManager.instance = new ToastManager();
}
return ToastManager.instance;
}
show(message: string, type: 'success' | 'error' | 'info' = 'info') {
const id = Date.now();
this.toasts.push({ id, message, type });
this.notify();
setTimeout(() => {
this.toasts = this.toasts.filter(t => t.id !== id);
this.notify();
}, 3000);
}
subscribe(listener: () => void) {
this.listeners.add(listener);
return () => this.listeners.delete(listener);
}
private notify() {
this.listeners.forEach(listener => listener());
}
getToasts() {
return this.toasts;
}
}
// ============ 3. 建造者模式 ============
// 场景:构建复杂的配置对象
class RequestBuilder {
private url: string = '';
private method: string = 'GET';
private headers: Record<string, string> = {};
private body: any = null;
private timeout: number = 5000;
private retries: number = 0;
setUrl(url: string): this {
this.url = url;
return this;
}
setMethod(method: string): this {
this.method = method;
return this;
}
addHeader(key: string, value: string): this {
this.headers[key] = value;
return this;
}
setBody(body: any): this {
this.body = body;
return this;
}
setTimeout(timeout: number): this {
this.timeout = timeout;
return this;
}
setRetries(retries: number): this {
this.retries = retries;
return this;
}
async execute() {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
try {
const response = await fetch(this.url, {
method: this.method,
headers: this.headers,
body: this.body ? JSON.stringify(this.body) : null,
signal: controller.signal
});
return response.json();
} finally {
clearTimeout(timeoutId);
}
}
}
// 使用
const data = await new RequestBuilder()
.setUrl('https://api.example.com/users')
.setMethod('POST')
.addHeader('Authorization', 'Bearer token')
.addHeader('Content-Type', 'application/json')
.setBody({ name: 'John', age: 30 })
.setTimeout(10000)
.setRetries(3)
.execute();
🏗️ 结构型模式
// ============ 4. 适配器模式 ============
// 场景:统一不同 API 的响应格式
// 旧 API 格式
interface OldApiResponse {
user_name: string;
user_age: number;
user_email: string;
}
// 新 API 格式
interface NewApiResponse {
name: string;
age: number;
email: string;
}
// 适配器
class ApiAdapter {
static toNewFormat(oldData: OldApiResponse): NewApiResponse {
return {
name: oldData.user_name,
age: oldData.user_age,
email: oldData.user_email
};
}
static toOldFormat(newData: NewApiResponse): OldApiResponse {
return {
user_name: newData.name,
user_age: newData.age,
user_email: newData.email
};
}
}
// 使用适配器统一数据格式
async function fetchUser(id: string): Promise<NewApiResponse> {
const response = await fetch(`/api/user/${id}`);
const data = await response.json();
// 检查响应格式并适配
if ('user_name' in data) {
return ApiAdapter.toNewFormat(data);
}
return data;
}
// ============ 5. 装饰器模式 ============
// 场景:给函数添加日志、缓存、性能监控等能力
// 日志装饰器
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(`[${new Date().toISOString()}] 调用 ${propertyKey}(${JSON.stringify(args)})`);
const result = originalMethod.apply(this, args);
console.log(`[${new Date().toISOString()}] ${propertyKey} 返回 ${JSON.stringify(result)}`);
return result;
};
return descriptor;
}
// 缓存装饰器
function cache(ttl: number = 60000) {
const cacheMap = new Map();
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = async function(...args: any[]) {
const key = `${propertyKey}:${JSON.stringify(args)}`;
const cached = cacheMap.get(key);
if (cached && Date.now() - cached.timestamp < ttl) {
console.log(`[缓存命中] ${key}`);
return cached.data;
}
const result = await originalMethod.apply(this, args);
cacheMap.set(key, { data: result, timestamp: Date.now() });
return result;
};
return descriptor;
};
}
// 重试装饰器
function retry(maxRetries: number = 3, delay: number = 1000) {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = async function(...args: any[]) {
let lastError: Error;
for (let i = 0; i <= maxRetries; i++) {
try {
return await originalMethod.apply(this, args);
} catch (error) {
lastError = error as Error;
console.log(`重试 ${i + 1}/${maxRetries}: ${error}`);
if (i < maxRetries) {
await new Promise(resolve => setTimeout(resolve, delay * Math.pow(2, i)));
}
}
}
throw lastError!;
};
return descriptor;
};
}
// 使用装饰器
class UserService {
@log
@cache(30000) // 缓存30秒
@retry(3, 1000)
async getUser(id: string) {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
}
// ============ 6. 代理模式 ============
// 场景:图片懒加载、访问控制、虚拟代理
// 虚拟代理:图片懒加载
class LazyImage {
private img: HTMLImageElement;
private placeholder: string;
constructor(private src: string, placeholder: string = 'data:image/svg+xml,...') {
this.placeholder = placeholder;
this.img = new Image();
}
render(container: HTMLElement) {
// 先显示占位图
const imgElement = document.createElement('img');
imgElement.src = this.placeholder;
container.appendChild(imgElement);
// 当图片进入视口时加载真实图片
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
this.img.onload = () => {
imgElement.src = this.src;
};
this.img.src = this.src;
observer.disconnect();
}
});
observer.observe(imgElement);
}
}
// 保护代理:权限控制
class AdminProxy {
constructor(private userService: UserService, private userRole: string) {}
async deleteUser(id: string) {
if (this.userRole !== 'admin') {
throw new Error('权限不足,只有管理员可以删除用户');
}
return this.userService.deleteUser(id);
}
async updateUser(id: string, data: any) {
if (this.userRole !== 'admin' && this.userRole !== 'editor') {
throw new Error('权限不足');
}
return this.userService.updateUser(id, data);
}
}
🔄 行为型模式
// ============ 7. 观察者模式 ============
// 场景:事件总线、状态管理、响应式系统
// 简易事件总线
class EventBus {
private listeners: Map<string, Set<Function>> = new Map();
on(event: string, callback: Function) {
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set());
}
this.listeners.get(event)!.add(callback);
// 返回取消订阅函数
return () => this.off(event, callback);
}
off(event: string, callback: Function) {
this.listeners.get(event)?.delete(callback);
}
emit(event: string, data?: any) {
this.listeners.get(event)?.forEach(callback => {
try {
callback(data);
} catch (error) {
console.error(`事件 ${event} 处理失败:`, error);
}
});
}
once(event: string, callback: Function) {
const wrapper = (data: any) => {
callback(data);
this.off(event, wrapper);
};
this.on(event, wrapper);
}
}
// 使用
const eventBus = new EventBus();
// 组件A订阅事件
const unsubscribe = eventBus.on('user-login', (user) => {
console.log('用户已登录:', user);
});
// 组件B触发事件
eventBus.emit('user-login', { id: 1, name: 'John' });
// 取消订阅
unsubscribe();
// ============ 8. 策略模式 ============
// 场景:表单验证、排序算法、支付方式
// 验证策略
interface ValidationStrategy {
validate(value: any): { valid: boolean; message?: string };
}
class RequiredStrategy implements ValidationStrategy {
validate(value: any) {
const valid = value !== undefined && value !== null && value !== '';
return { valid, message: valid ? undefined : '此字段为必填项' };
}
}
class EmailStrategy implements ValidationStrategy {
private emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
validate(value: string) {
const valid = this.emailRegex.test(value);
return { valid, message: valid ? undefined : '请输入有效的邮箱地址' };
}
}
class MinLengthStrategy implements ValidationStrategy {
constructor(private min: number) {}
validate(value: string) {
const valid = value && value.length >= this.min;
return { valid, message: valid ? undefined : `最少需要 ${this.min} 个字符` };
}
}
class MaxLengthStrategy implements ValidationStrategy {
constructor(private max: number) {}
validate(value: string) {
const valid = !value || value.length <= this.max;
return { valid, message: valid ? undefined : `最多允许 ${this.max} 个字符` };
}
}
// 验证器上下文
class Validator {
private strategies: Map<string, ValidationStrategy[]> = new Map();
addStrategy(field: string, strategy: ValidationStrategy) {
if (!this.strategies.has(field)) {
this.strategies.set(field, []);
}
this.strategies.get(field)!.push(strategy);
}
validate(data: Record<string, any>) {
const errors: Record<string, string[]> = {};
for (const [field, strategies] of this.strategies) {
const fieldErrors: string[] = [];
for (const strategy of strategies) {
const result = strategy.validate(data[field]);
if (!result.valid && result.message) {
fieldErrors.push(result.message);
}
}
if (fieldErrors.length > 0) {
errors[field] = fieldErrors;
}
}
return {
valid: Object.keys(errors).length === 0,
errors
};
}
}
// 使用
const validator = new Validator();
validator.addStrategy('email', new RequiredStrategy());
validator.addStrategy('email', new EmailStrategy());
validator.addStrategy('password', new RequiredStrategy());
validator.addStrategy('password', new MinLengthStrategy(6));
validator.addStrategy('password', new MaxLengthStrategy(20));
const result = validator.validate({
email: 'invalid-email',
password: '123'
});
console.log(result);
// { valid: false, errors: { email: ['请输入有效的邮箱地址'], password: ['最少需要 6 个字符'] } }
// ============ 9. 状态模式 ============
// 场景:有限状态机、工作流、订单状态管理
interface OrderState {
handle(): void;
cancel(): void;
getStatus(): string;
}
class PendingState implements OrderState {
constructor(private order: OrderContext) {}
handle() {
console.log('订单已支付,进入处理中状态');
this.order.setState(this.order.processingState);
}
cancel() {
console.log('订单已取消');
this.order.setState(this.order.cancelledState);
}
getStatus() { return '待支付'; }
}
class ProcessingState implements OrderState {
constructor(private order: OrderContext) {}
handle() {
console.log('订单已发货,进入配送状态');
this.order.setState(this.order.shippingState);
}
cancel() {
console.log('处理中的订单无法取消');
}
getStatus() { return '处理中'; }
}
class ShippingState implements OrderState {
constructor(private order: OrderContext) {}
handle() {
console.log('订单已完成');
this.order.setState(this.order.completedState);
}
cancel() {
console.log('配送中的订单无法取消');
}
getStatus() { return '配送中'; }
}
class CompletedState implements OrderState {
handle() { console.log('订单已完成,无法变更'); }
cancel() { console.log('已完成订单无法取消'); }
getStatus() { return '已完成'; }
}
class CancelledState implements OrderState {
handle() { console.log('已取消订单无法处理'); }
cancel() { console.log('订单已取消'); }
getStatus() { return '已取消'; }
}
class OrderContext {
pendingState: OrderState;
processingState: OrderState;
shippingState: OrderState;
completedState: OrderState;
cancelledState: OrderState;
private currentState: OrderState;
constructor() {
this.pendingState = new PendingState(this);
this.processingState = new ProcessingState(this);
this.shippingState = new ShippingState(this);
this.completedState = new CompletedState(this);
this.cancelledState = new CancelledState(this);
this.currentState = this.pendingState;
}
setState(state: OrderState) {
this.currentState = state;
}
handle() {
this.currentState.handle();
}
cancel() {
this.currentState.cancel();
}
getStatus() {
return this.currentState.getStatus();
}
}
// 使用
const order = new OrderContext();
console.log(order.getStatus()); // 待支付
order.handle(); // 订单已支付,进入处理中状态
console.log(order.getStatus()); // 处理中
order.handle(); // 订单已发货,进入配送状态
console.log(order.getStatus()); // 配送中
order.cancel(); // 配送中的订单无法取消
🎯 今日挑战
实现一个完整的表单系统,要求:
- 使用策略模式实现多种验证规则
- 使用观察者模式实现表单联动
- 使用代理模式实现表单提交防抖
- 使用状态模式管理表单状态(编辑/预览/提交)
- 使用建造者模式构建复杂表单配置
// 使用示例
const form = new FormBuilder()
.addField('username', 'text', { label: '用户名', required: true, minLength: 3 })
.addField('email', 'email', { label: '邮箱', required: true })
.addField('age', 'number', { label: '年龄', min: 18, max: 99 })
.addField('role', 'select', { label: '角色', options: ['admin', 'user', 'guest'] })
.build();
form.on('change', (data) => console.log('表单数据变更:', data));
form.on('submit', async (data) => {
await api.submit(data);
});
form.render('#app');
明日预告:前端监控与错误追踪 - 构建企业级监控系统
💡 设计模式箴言:"模式是过往经验的结晶,但过度使用模式会使代码复杂化。保持简单,只在必要时引入模式。"