JavaScript/TypeScript 设计模式实战指南:2026 年最值得掌握的 10 个模式

深入解析 JavaScript 和 TypeScript 中最实用的设计模式,包含 Singleton、Observer、Strategy、Proxy 等 10 个模式的完整可运行代码示例,附性能对比与避坑指南。

前端开发 2026-06-08 15 分钟

设计模式(Design Patterns)是软件工程中经过验证的解决方案模板。2026 年的今天,随着 TypeScript 成为前端和 Node.js 开发的事实标准,设计模式在类型安全的加持下焕发出新的生命力。根据 State of JS 2025 调查,超过 87% 的前端项目使用 TypeScript,而掌握设计模式的开发者在架构评审中通过率高出 40% 以上。

本文不是 GoF 23 种模式的简单翻译,而是精选了 10 个在 JavaScript/TypeScript 日常开发中真正高频使用 的模式,每个模式都附带完整可运行代码、实际应用场景和常见坑点。

🏗️ 一、创建型模式:控制对象的诞生方式

创建型模式解决的核心问题是:如何灵活、高效地创建对象。在 JavaScript 中,由于函数是一等公民且没有传统意义上的「类」(ES6 class 只是语法糖),创建型模式的实现方式与 Java/C++ 有显著差异。

🔹 1. Singleton 模式 — 全局唯一实例

Singleton 确保一个类只有一个实例,并提供全局访问点。在前端开发中,它常用于管理全局状态、配置对象、数据库连接池等。

❌ 错误写法:全局变量

// 危险!任何代码都可以修改
let appConfig = { apiUrl: 'https://api.example.com' };

✅ 正确写法:TypeScript Singleton

// TypeScript Singleton — 使用闭包 + 私有构造函数
class AppConfig {
  private static instance: AppConfig;
  public readonly apiUrl: string;
  public readonly timeout: number;

  private constructor() {
    this.apiUrl = process.env.API_URL ?? 'https://api.example.com';
    this.timeout = Number(process.env.TIMEOUT) || 5000;
  }

  static getInstance(): AppConfig {
    if (!AppConfig.instance) {
      AppConfig.instance = new AppConfig();
    }
    return AppConfig.instance;
  }
}

// 使用
const config = AppConfig.getInstance();
console.log(config.apiUrl); // https://api.example.com

💡 **提示:**在 Node.js 中,模块本身就是单例的(CommonJS 的 require 缓存机制)。如果你只需要一个配置对象,直接 export default 一个对象实例就够了。Singleton 模式更适合需要延迟初始化(Lazy Initialization)的场景。

适用场景: 日志服务、数据库连接池、全局配置、缓存管理器

🔹 2. Factory 模式 — 灵活的对象创建

Factory 模式将对象创建逻辑封装起来,调用方不需要知道具体的类。在 TypeScript 中,结合泛型(Generics)和接口(Interface),Factory 可以做到既灵活又类型安全。

// 类型安全的 Factory 模式
interface Notification {
  send(message: string): void;
}

class EmailNotification implements Notification {
  send(message: string) {
    console.log(`📧 Email: ${message}`);
  }
}

class SMSNotification implements Notification {
  send(message: string) {
    console.log(`📱 SMS: ${message}`);
  }
}

class PushNotification implements Notification {
  send(message: string) {
    console.log(`🔔 Push: ${message}`);
  }
}

// Factory — 使用 Map 注册,支持动态扩展
class NotificationFactory {
  private static creators = new Map<string, () => Notification>([
    ['email', () => new EmailNotification()],
    ['sms', () => new SMSNotification()],
    ['push', () => new PushNotification()],
  ]);

  static register(type: string, creator: () => Notification) {
    this.creators.set(type, creator);
  }

  static create(type: string): Notification {
    const creator = this.creators.get(type);
    if (!creator) throw new Error(`Unknown notification type: ${type}`);
    return creator();
  }
}

// 使用
const notifier = NotificationFactory.create('sms');
notifier.send('您的验证码是 123456'); // 📱 SMS: 您的验证码是 123456

📌 **记住:**Factory 模式的核心价值在于 解耦。当你的代码中出现大量的 if-elseswitch 来判断类型并创建对象时,就该考虑用 Factory 了。

🔌 二、结构型模式:优雅地组合对象

结构型模式关注的是 对象之间的组合方式,让复杂的结构变得更清晰、更易维护。

🔹 3. Proxy 模式 — 智能代理

JavaScript 原生支持 Proxy(new Proxy()),这使得 Proxy 模式的实现异常简洁。它可以在不修改原始对象的情况下,拦截和自定义对象的操作。

// 使用 Proxy 实现 API 请求缓存
function createCachedFetcher(ttlMs: number = 60_000) {
  const cache = new Map<string, { data: unknown; expiry: number }>();

  return new Proxy(fetch, {
    apply(target, thisArg, args: [string | URL | Request, RequestInit?]) {
      const url = typeof args[0] === 'string' ? args[0] : args[0].toString();
      const cached = cache.get(url);

      if (cached && cached.expiry > Date.now()) {
        console.log(`🎯 Cache hit: ${url}`);
        return Promise.resolve(new Response(JSON.stringify(cached.data)));
      }

      console.log(`🌐 Fetching: ${url}`);
      return Reflect.apply(target, thisArg, args).then(async (res: Response) => {
        const cloned = res.clone();
        const data = await cloned.json();
        cache.set(url, { data, expiry: Date.now() + ttlMs });
        return res;
      });
    },
  });
}

// 使用
const cachedFetch = createCachedFetcher(30_000);
await cachedFetch('https://api.example.com/users'); // 🌐 Fetching
await cachedFetch('https://api.example.com/users'); // 🎯 Cache hit

⚠️ **警告:**Proxy 的 apply trap 只能拦截函数调用,不能拦截 new 操作符。如果你需要拦截构造函数,需要使用 construct trap。另外,Proxy 有轻微的性能开销,在热路径(Hot Path)上要谨慎使用。

🔹 4. Decorator 模式 — 动态增强功能

Decorator 模式在不修改原始代码的情况下,为对象添加新功能。TypeScript 5.0+ 的 Stage 3 Decorator 规范让这个模式更加标准化。

// TypeScript Stage 3 Decorator — 方法执行时间监控
function LogExecutionTime(
  target: (this: unknown, ...args: unknown[]) => unknown,
  context: ClassMethodDecoratorContext
) {
  return function (this: unknown, ...args: unknown[]) {
    const start = performance.now();
    const result = target.call(this, ...args);
    const duration = performance.now() - start;
    console.log(`⏱️ ${String(context.name)} took ${duration.toFixed(2)}ms`);
    return result;
  };
}

class UserService {
  @LogExecutionTime
  async fetchUsers(): Promise<string[]> {
    // 模拟 API 调用
    await new Promise(r => setTimeout(r, 100));
    return ['Alice', 'Bob', 'Charlie'];
  }
}

const service = new UserService();
await service.fetchUsers();
// ⏱️ fetchUsers took 100.23ms

🔹 5. Adapter 模式 — 接口转换器

当你需要让两个不兼容的接口协同工作时,Adapter 模式就是你的救星。在前端开发中,它常用于封装第三方库或适配不同版本的 API。

// 旧版 API 返回格式
interface OldUserFormat {
  user_name: string;
  user_email: string;
  created_at: number; // Unix timestamp
}

// 新版 API 返回格式
interface NewUserFormat {
  displayName: string;
  email: string;
  createdAt: Date;
}

// Adapter — 将旧格式转换为新格式
class UserAdapter {
  static toNewFormat(old: OldUserFormat): NewUserFormat {
    return {
      displayName: old.user_name,
      email: old.user_email,
      createdAt: new Date(old.created_at * 1000),
    };
  }

  static toOldFormat(newUser: NewUserFormat): OldUserFormat {
    return {
      user_name: newUser.displayName,
      user_email: newUser.email,
      created_at: Math.floor(newUser.createdAt.getTime() / 1000),
    };
  }
}

// 使用 — 无缝对接两个版本的 API
const oldData: OldUserFormat = {
  user_name: '张三',
  user_email: 'zhangsan@example.com',
  created_at: 1717958400,
};
const adapted = UserAdapter.toNewFormat(oldData);
console.log(adapted.displayName); // 张三
console.log(adapted.createdAt); // 2025-06-10T...

🎭 三、行为型模式:定义对象间的通信方式

行为型模式关注的是 对象之间的职责分配和通信,是构建可维护系统的关键。

🔹 6. Observer 模式 — 事件驱动的基石

Observer(观察者)模式是前端开发中使用最频繁的模式之一。React 的 useState、Vue 的响应式系统、DOM 的 addEventListener,本质上都是 Observer 模式的变体。

// 类型安全的 EventEmitter
type EventMap = Record<string, unknown[]>;

class TypedEventEmitter<Events extends EventMap> {
  private listeners = new Map<keyof Events, Set<Function>>();

  on<K extends keyof Events>(event: K, listener: (...args: Events[K]) => void): () => void {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, new Set());
    }
    this.listeners.get(event)!.add(listener);

    // 返回取消订阅函数
    return () => this.listeners.get(event)?.delete(listener);
  }

  emit<K extends keyof Events>(event: K, ...args: Events[K]): void {
    this.listeners.get(event)?.forEach(fn => fn(...args));
  }
}

// 使用 — 定义事件类型
interface AppEvents {
  'user:login': [userId: string, timestamp: Date];
  'user:logout': [userId: string];
  'error': [error: Error];
}

const emitter = new TypedEventEmitter<AppEvents>();

const unsubscribe = emitter.on('user:login', (userId, timestamp) => {
  console.log(`✅ User ${userId} logged in at ${timestamp}`);
});

emitter.emit('user:login', 'user-123', new Date());
// ✅ User user-123 logged in at Mon Jun 09 2026 ...

unsubscribe(); // 取消订阅

💡 **提示:**上面的实现返回了取消订阅函数(类似 React 的 useEffect cleanup),这是避免内存泄漏的最佳实践。永远记住:订阅了就要取消

🔹 7. Strategy 模式 — 算法的灵活切换

Strategy 模式定义一系列算法,将它们封装起来,使它们可以互相替换。在前端开发中,它常用于表单验证、排序策略、数据格式化等场景。

// 表单验证策略
interface ValidationStrategy {
  validate(value: string): string | null;
}

class EmailValidation implements ValidationStrategy {
  validate(value: string): string | null {
    const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return re.test(value) ? null : '请输入有效的邮箱地址';
  }
}

class PhoneValidation implements ValidationStrategy {
  validate(value: string): string | null {
    const re = /^1[3-9]\d{9}$/;
    return re.test(value) ? null : '请输入有效的手机号码';
  }
}

class PasswordValidation implements ValidationStrategy {
  validate(value: string): string | null {
    if (value.length < 8) return '密码至少 8 位';
    if (!/[A-Z]/.test(value)) return '密码需包含大写字母';
    if (!/[0-9]/.test(value)) return '密码需包含数字';
    return null;
  }
}

// 表单验证器 — 动态切换验证策略
class FormValidator {
  private strategies = new Map<string, ValidationStrategy>();

  addField(field: string, strategy: ValidationStrategy) {
    this.strategies.set(field, strategy);
    return this; // 支持链式调用
  }

  validate(data: Record<string, string>): Map<string, string> {
    const errors = new Map<string, string>();
    for (const [field, strategy] of this.strategies) {
      const error = strategy.validate(data[field] ?? '');
      if (error) errors.set(field, error);
    }
    return errors;
  }
}

// 使用
const validator = new FormValidator()
  .addField('email', new EmailValidation())
  .addField('phone', new PhoneValidation())
  .addField('password', new PasswordValidation());

const errors = validator.validate({
  email: 'invalid',
  phone: '13800138000',
  password: 'Strong1Pass',
});

console.log(errors.size); // 1(只有 email 验证失败)

🔹 8. Command 模式 — 操作的封装与撤销

Command 模式将操作封装为对象,支持撤销(Undo)、重做(Redo)和操作队列。在富文本编辑器、图形编辑器、IDE 等应用中极为常见。

// 简单的撤销/重做系统
interface Command {
  execute(): void;
  undo(): void;
}

class TextEditor {
  content = '';

  insert(text: string, position: number) {
    this.content =
      this.content.slice(0, position) + text + this.content.slice(position);
  }

  delete(start: number, length: number): string {
    const deleted = this.content.slice(start, start + length);
    this.content = this.content.slice(0, start) + this.content.slice(start + length);
    return deleted;
  }
}

class InsertCommand implements Command {
  constructor(
    private editor: TextEditor,
    private text: string,
    private position: number
  ) {}

  execute() {
    this.editor.insert(this.text, this.position);
  }

  undo() {
    this.editor.delete(this.position, this.text.length);
  }
}

class DeleteCommand implements Command {
  private deletedText = '';

  constructor(
    private editor: TextEditor,
    private start: number,
    private length: number
  ) {}

  execute() {
    this.deletedText = this.editor.delete(this.start, this.length);
  }

  undo() {
    this.editor.insert(this.deletedText, this.start);
  }
}

// 命令管理器 — 支持撤销/重做
class CommandManager {
  private history: Command[] = [];
  private redoStack: Command[] = [];

  execute(command: Command) {
    command.execute();
    this.history.push(command);
    this.redoStack = []; // 新操作清空 redo 栈
  }

  undo() {
    const command = this.history.pop();
    if (command) {
      command.undo();
      this.redoStack.push(command);
    }
  }

  redo() {
    const command = this.redoStack.pop();
    if (command) {
      command.execute();
      this.history.push(command);
    }
  }
}

// 使用
const editor = new TextEditor();
const manager = new CommandManager();

manager.execute(new InsertCommand(editor, 'Hello', 0));
console.log(editor.content); // Hello

manager.execute(new InsertCommand(editor, ' World', 5));
console.log(editor.content); // Hello World

manager.undo();
console.log(editor.content); // Hello

manager.redo();
console.log(editor.content); // Hello World

🔹 9. State 模式 — 状态机的优雅实现

State 模式将对象的每种状态封装为独立的类,状态转换逻辑分散到各个状态类中。相比大量的 if-elseswitch,State 模式让状态管理更加清晰和可扩展。

// 订单状态机 — 使用 State 模式管理订单生命周期
interface OrderState {
  next(order: OrderContext): void;
  cancel(order: OrderContext): void;
  getStatus(): string;
}

class PendingState implements OrderState {
  next(order: OrderContext) {
    console.log('⏳ 订单已支付,进入处理中');
    order.setState(new ProcessingState());
  }
  cancel(order: OrderContext) {
    console.log('❌ 订单已取消');
    order.setState(new CancelledState());
  }
  getStatus() { return '待支付'; }
}

class ProcessingState implements OrderState {
  next(order: OrderContext) {
    console.log('📦 订单已发货');
    order.setState(new ShippedState());
  }
  cancel(order: OrderContext) {
    console.log('❌ 订单已取消,退款处理中');
    order.setState(new CancelledState());
  }
  getStatus() { return '处理中'; }
}

class ShippedState implements OrderState {
  next(order: OrderContext) {
    console.log('✅ 订单已签收');
    order.setState(new DeliveredState());
  }
  cancel() {
    console.log('⚠️ 已发货的订单无法直接取消,请申请退货');
  }
  getStatus() { return '已发货'; }
}

class DeliveredState implements OrderState {
  next() { console.log('⚠️ 订单已完成,无法继续操作'); }
  cancel() { console.log('⚠️ 订单已完成,请申请售后'); }
  getStatus() { return '已签收'; }
}

class CancelledState implements OrderState {
  next() { console.log('⚠️ 订单已取消,无法继续操作'); }
  cancel() { console.log('⚠️ 订单已取消'); }
  getStatus() { return '已取消'; }
}

class OrderContext {
  private state: OrderState;

  constructor() {
    this.state = new PendingState();
  }

  setState(state: OrderState) { this.state = state; }
  next() { this.state.next(this); }
  cancel() { this.state.cancel(this); }
  getStatus() { return this.state.getStatus(); }
}

// 使用
const order = new OrderContext();
console.log(order.getStatus()); // 待支付
order.next();     // ⏳ 订单已支付,进入处理中
order.next();     // 📦 订单已发货
order.cancel();   // ⚠️ 已发货的订单无法直接取消,请申请退货
order.next();     // ✅ 订单已签收
console.log(order.getStatus()); // 已签收

⚡ **关键结论:**State 模式的核心优势在于 状态转换逻辑内聚。每种状态只关心自己能转换到哪些状态,新增状态时只需添加一个类,不需要修改现有代码(符合开闭原则)。

🔹 10. Iterator 模式 — 统一的遍历协议

JavaScript 原生支持 Iterator 协议(Symbol.iterator),这使得 Iterator 模式的实现异常简洁。ES2025 新增的 Iterator Helpers(mapfiltertake 等)更让这个模式如虎添翼。

// 自定义可迭代对象 — 分页数据懒加载
class PaginatedFetcher<T> implements AsyncIterable<T> {
  private page = 0;

  constructor(
    private fetchPage: (page: number) => Promise<{ data: T[]; hasMore: boolean }>,
  ) {}

  [Symbol.asyncIterator](): AsyncIterator<T> {
    let buffer: T[] = [];
    let done = false;

    return {
      next: async () => {
        // 缓冲区为空时加载下一页
        if (buffer.length === 0 && !done) {
          const result = await this.fetchPage(this.page++);
          buffer = result.data;
          if (!result.hasMore) done = true;
        }

        if (buffer.length === 0) {
          return { value: undefined, done: true };
        }

        return { value: buffer.shift()!, done: false };
      },
    };
  }
}

// 使用 — 像遍历数组一样遍历分页数据
const users = new PaginatedFetcher(async (page) => {
  const res = await fetch(`/api/users?page=${page}`);
  return res.json();
});

// 使用 for await...of 遍历
for await (const user of users) {
  console.log(user);
  if (someCondition) break; // 支持提前退出,不会加载剩余页
}

📌 **记住:**Iterator 的核心优势是 惰性求值(Lazy Evaluation)。数据只有在需要时才被加载和处理,这对于处理大数据集或分页 API 非常有用。ES2025 的 Iterator Helpers 让你可以链式调用 .map().filter().take(n) 等方法,且不会创建中间数组。

📊 四、模式对比与选型指南

选择合适的设计模式取决于具体场景。下表列出了本文 10 个模式的核心特征和适用场景:

模式 类型 核心作用 典型场景 复杂度
Singleton 创建 全局唯一实例 配置管理、连接池
Factory 创建 解耦对象创建 工厂方法、插件系统 ⭐⭐
Proxy 结构 拦截操作 缓存、权限控制、日志 ⭐⭐
Decorator 结构 动态增强功能 AOP、日志、性能监控 ⭐⭐
Adapter 结构 接口转换 第三方库封装、API 适配
Observer 行为 事件驱动通信 状态管理、事件系统 ⭐⭐
Strategy 行为 算法灵活切换 验证、排序、格式化 ⭐⭐
Command 行为 操作封装 撤销/重做、宏命令 ⭐⭐⭐
State 行为 状态机 UI 状态、工作流 ⭐⭐⭐
Iterator 行为 统一遍历接口 数据集合遍历

⚡ 五、实战避坑指南

⚠️ 坑点 1:Singleton 与模块热更新的冲突

在 Vite/Webpack 的 HMR(Hot Module Replacement)环境下,Singleton 的实例会随模块重新执行而被重置。如果你的 Singleton 管理了运行时状态(如用户登录信息),热更新后状态会丢失。

✅ 解决方案: 将关键状态存储在 globalThiswindow 上,或者使用 import.meta.hot?.accept() 保持状态。

⚠️ 坑点 2:Observer 模式的内存泄漏

忘记取消订阅是 Observer 模式最常见的 bug。在 React 中,如果在 useEffect 中订阅了事件但没有在 cleanup 中取消,组件卸载后监听器仍然存在。

// ❌ 错误写法 — 内存泄漏
useEffect(() => {
  emitter.on('data', handleData);
}, []);

// ✅ 正确写法 — 组件卸载时取消订阅
useEffect(() => {
  const unsubscribe = emitter.on('data', handleData);
  return unsubscribe;
}, []);

⚠️ 坑点 3:Proxy 的 this 陷阱

使用 new Proxy() 包装对象时,如果原始方法依赖 this 指向原始对象,代理后 this 会指向 Proxy 本身,导致意外行为。

const target = {
  name: 'Target',
  getName() {
    return this.name;
  },
};

const proxy = new Proxy(target, {
  get(obj, prop) {
    const value = Reflect.get(obj, prop);
    // ⚠️ 必须绑定原始对象,否则 this 指向 proxy
    return typeof value === 'function' ? value.bind(obj) : value;
  },
});

console.log(proxy.getName()); // Target(正确)

🎯 六、总结与最佳实践

设计模式不是银弹,滥用比不用更危险。以下是我的建议:

  • 先写代码,再重构为模式 — 不要在一开始就强行套用模式,等代码出现「坏味道」(如重复的 if-else、紧耦合)时再重构
  • 优先使用语言内置特性 — JavaScript 的 ProxyEventEmitterSymbol.iterator 等已经内置了 Proxy、Observer、Iterator 模式
  • TypeScript 泛型让模式更安全 — 利用泛型约束(Generic Constraints)可以在编译期捕获类型错误
  • 不要为了模式而模式 — 如果一个简单的函数就能解决问题,就不需要 Factory
  • 不要在小项目中过度设计 — 设计模式的价值在代码量超过 5000 行后才会显现

⚡ **关键结论:**设计模式的本质是 管理复杂度。当你的代码开始变得难以维护时,选择合适的模式进行重构,比从一开始就「完美设计」更务实。

相关工具推荐:

📚 相关文章