JavaScript 设计模式完全指南

JavaScript 设计模式完全指南

设计模式是解决常见软件设计问题的可复用方案,掌握它们能写出更优雅的代码


前言

在设计 JavaScript 应用时,我们经常会遇到一些重复出现的问题:

  • 如何确保一个类只有一个实例?
  • 如何在不修改原有代码的情况下扩展功能?
  • 如何解耦对象之间的依赖关系?

设计模式就是这些问题的解决方案。它们不是代码模板,而是来自大量开发实践的经验总结。

下面结合具体场景讲解 JavaScript 中的核心设计模式,帮助你理解每种模式的适用场景和实现方式。


一、设计模式的本质

什么是设计模式?

设计模式是解决常见软件设计问题的可复用方案。它们具有以下特点:

  • 来自实践,经过验证
  • 不是代码模板,而是思路和方法论
  • 可以提高代码的可维护性和可扩展性

三大分类

分类 作用 代表模式
创建型 对象创建机制 单例、工厂、建造者
结构型 对象组合方式 适配器、装饰器、代理
行为型 对象通信方式 观察者、策略、命令

现代 JavaScript 的特殊性

ES6+ 的 class、module、Promise 等特性,让某些传统模式有了新的实现方式:

  • ES6 Module 天然就是单例
  • Promise 简化了回调模式
  • class 语法让某些模式更简洁

二、创建型模式

1. 单例模式(Singleton)

核心思想:确保一个类只有一个实例,并提供全局访问点。

场景:全局状态管理、配置对象、数据库连接池。

javascript 复制代码
// ES6 实现
class Store {
  constructor() {
    if (Store.instance) {
      return Store.instance;
    }
    this.state = {};
    Store.instance = this;
  }
  
  getState(key) {
    return this.state[key];
  }
  
  setState(key, value) {
    this.state[key] = value;
  }
}

// 使用
const store1 = new Store();
const store2 = new Store();
console.log(store1 === store2); // true

现代替代方案:ES6 Module 天然单例

javascript 复制代码
// store.js
const state = {};

export function getState(key) {
  return state[key];
}

export function setState(key, value) {
  state[key] = value;
}

// 导入的永远是同一个实例

2. 工厂模式(Factory)

核心思想:封装对象创建逻辑,根据条件返回不同类型的对象。

场景:需要根据类型创建不同对象、对象创建逻辑复杂。

javascript 复制代码
// 简单工厂
class Dog {
  speak() {
    return '汪汪';
  }
}

class Cat {
  speak() {
    return '喵喵';
  }
}

function createAnimal(type) {
  switch (type) {
    case 'dog':
      return new Dog();
    case 'cat':
      return new Cat();
    default:
      throw new Error('Unknown animal type');
  }
}

// 使用
const dog = createAnimal('dog');
console.log(dog.speak()); // 汪汪

优势

  • 创建逻辑集中管理
  • 易于扩展新类型
  • 调用者不需要知道具体类

3. 建造者模式(Builder)

核心思想:分步骤创建复杂对象,分离构造和表示。

场景:对象有多个可选配置、创建过程复杂。

javascript 复制代码
class PizzaBuilder {
  constructor() {
    this.pizza = {};
  }
  
  setDough(dough) {
    this.pizza.dough = dough;
    return this;
  }
  
  setSauce(sauce) {
    this.pizza.sauce = sauce;
    return this;
  }
  
  setTopping(topping) {
    this.pizza.topping = topping;
    return this;
  }
  
  build() {
    return this.pizza;
  }
}

// 使用
const pizza = new PizzaBuilder()
  .setDough('thin')
  .setSauce('tomato')
  .setTopping('cheese')
  .build();

console.log(pizza);
// { dough: 'thin', sauce: 'tomato', topping: 'cheese' }

三、结构型模式

1. 适配器模式(Adapter)

核心思想:将一个类的接口转换成客户期望的另一个接口。

场景:复用现有类但接口不兼容、第三方库集成。

javascript 复制代码
// 旧接口
class OldPayment {
  pay(amount) {
    console.log(`支付 ${amount} 元(旧方式)`);
  }
}

// 新接口期望
class NewPayment {
  constructor(adapter) {
    this.adapter = adapter;
  }
  
  processPayment(amount) {
    this.adapter.pay(amount);
  }
}

// 适配器
class PaymentAdapter {
  constructor(oldPayment) {
    this.oldPayment = oldPayment;
  }
  
  pay(amount) {
    this.oldPayment.pay(amount);
  }
}

// 使用
const oldPayment = new OldPayment();
const adapter = new PaymentAdapter(oldPayment);
const newPayment = new NewPayment(adapter);
newPayment.processPayment(100);

2. 装饰器模式(Decorator)

核心思想:动态给对象添加功能,而不改变其结构。

场景:日志记录、权限控制、性能监控。

javascript 复制代码
// 基础类
class Coffee {
  cost() {
    return 5;
  }
}

// 装饰器
class MilkDecorator {
  constructor(coffee) {
    this.coffee = coffee;
  }
  
  cost() {
    return this.coffee.cost() + 2;
  }
}

class SugarDecorator {
  constructor(coffee) {
    this.coffee = coffee;
  }
  
  cost() {
    return this.coffee.cost() + 1;
  }
}

// 使用
let coffee = new Coffee();
coffee = new MilkDecorator(coffee);
coffee = new SugarDecorator(coffee);
console.log(coffee.cost()); // 8

现代替代方案:ES6 装饰器语法(Stage 3)

javascript 复制代码
function logged(target, context) {
  return function(...args) {
    console.log(`Calling ${context.name}`);
    return target(...args);
  };
}

class MyClass {
  @logged
  myMethod() {
    // ...
  }
}

3. 代理模式(Proxy)

核心思想:控制对对象的访问,在访问前后添加额外逻辑。

场景:懒加载、访问控制、缓存、日志。

javascript 复制代码
// 使用 ES6 Proxy
const target = {
  name: 'John',
  age: 30
};

const handler = {
  get(target, prop) {
    console.log(`访问属性:${prop}`);
    return target[prop];
  },
  set(target, prop, value) {
    console.log(`设置属性:${prop} = ${value}`);
    target[prop] = value;
    return true;
  }
};

const proxy = new Proxy(target, handler);

// 使用
proxy.name; // 访问属性:name
proxy.age = 31; // 设置属性:age = 31

四、行为型模式

1. 观察者模式(Observer)

核心思想:一对多依赖,一个对象状态改变时自动通知所有依赖者。

场景:事件系统、响应式数据、消息队列。

javascript 复制代码
class Subject {
  constructor() {
    this.observers = [];
  }
  
  subscribe(observer) {
    this.observers.push(observer);
  }
  
  unsubscribe(observer) {
    this.observers = this.observers.filter(o => o !== observer);
  }
  
  notify(data) {
    this.observers.forEach(observer => observer.update(data));
  }
}

class Observer {
  update(data) {
    console.log(`收到通知:${data}`);
  }
}

// 使用
const subject = new Subject();
const observer1 = new Observer();
const observer2 = new Observer();

subject.subscribe(observer1);
subject.subscribe(observer2);
subject.notify('状态改变');
// 收到通知:状态改变
// 收到通知:状态改变

现代替代方案:RxJS、Vue 响应式系统

2. 策略模式(Strategy)

核心思想:封装可互换的算法,让算法独立于使用它的客户端。

场景:多种排序/验证策略切换、支付方式选择。

javascript 复制代码
// 策略接口
class PaymentStrategy {
  pay(amount) {
    throw new Error('Must implement pay()');
  }
}

// 具体策略
class CreditCardPayment extends PaymentStrategy {
  pay(amount) {
    console.log(`信用卡支付 ${amount} 元`);
  }
}

class PayPalPayment extends PaymentStrategy {
  pay(amount) {
    console.log(`PayPal 支付 ${amount} 元`);
  }
}

// 上下文
class ShoppingCart {
  constructor(paymentStrategy) {
    this.paymentStrategy = paymentStrategy;
  }
  
  setPaymentStrategy(strategy) {
    this.paymentStrategy = strategy;
  }
  
  checkout(amount) {
    this.paymentStrategy.pay(amount);
  }
}

// 使用
const cart = new ShoppingCart(new CreditCardPayment());
cart.checkout(100); // 信用卡支付 100 元

cart.setPaymentStrategy(new PayPalPayment());
cart.checkout(200); // PayPal 支付 200 元

3. 命令模式(Command)

核心思想:将请求封装为对象,支持撤销、重做等操作。

场景:文本编辑器、宏命令、任务队列。

javascript 复制代码
// 命令接口
class Command {
  execute() {}
  undo() {}
}

// 具体命令
class AddTextCommand extends Command {
  constructor(editor, text) {
    super();
    this.editor = editor;
    this.text = text;
  }
  
  execute() {
    this.editor.addText(this.text);
  }
  
  undo() {
    this.editor.removeText(this.text);
  }
}

// 调用者
class Editor {
  constructor() {
    this.text = '';
    this.history = [];
  }
  
  addText(text) {
    this.text += text;
  }
  
  removeText(text) {
    this.text = this.text.replace(text, '');
  }
  
  executeCommand(command) {
    command.execute();
    this.history.push(command);
  }
  
  undo() {
    const command = this.history.pop();
    if (command) {
      command.undo();
    }
  }
}

// 使用
const editor = new Editor();
const command = new AddTextCommand(editor, 'Hello');
editor.executeCommand(command);
console.log(editor.text); // Hello

editor.undo();
console.log(editor.text); // 空

五、模式选择建议

问题 推荐模式
确保唯一实例 单例模式
创建逻辑复杂 工厂模式、建造者模式
接口不兼容 适配器模式
动态添加功能 装饰器模式
控制对象访问 代理模式
一对多通知 观察者模式
算法可切换 策略模式
支持撤销重做 命令模式

使用原则

  1. 不要过度设计 - 简单问题用简单方案
  2. 理解后再使用 - 不要为了用模式而用模式
  3. 结合语言特性 - 利用 ES6+ 新特性简化实现
  4. 保持代码可读 - 模式应该让代码更清晰,而不是更复杂

六、实战案例

案例:电商购物车系统

背景:电商平台需要实现购物车功能,支持多种支付方式,且购物车状态需要全局唯一。

问题

  • 购物车状态需要在整个应用中保持唯一
  • 支付方式可能随时扩展(支付宝、微信、信用卡等)
  • 需要解耦购物车逻辑和支付逻辑

实现方案:结合单例模式、策略模式和工厂模式

javascript 复制代码
// 商品类
class Product {
  constructor(name, price) {
    this.name = name;
    this.price = price;
  }
}

// 支付策略接口
class PaymentStrategy {
  pay(amount) {
    throw new Error('Must implement pay()');
  }
}

// 具体支付策略
class AlipayPayment extends PaymentStrategy {
  pay(amount) {
    console.log(`支付宝支付 ${amount} 元`);
  }
}

class WechatPayment extends PaymentStrategy {
  pay(amount) {
    console.log(`微信支付 ${amount} 元`);
  }
}

// 购物车(单例模式)
class ShoppingCart {
  constructor() {
    if (ShoppingCart.instance) {
      return ShoppingCart.instance;
    }
    this.items = [];
    this.paymentStrategy = null;
    ShoppingCart.instance = this;
  }
  
  addItem(product) {
    this.items.push(product);
  }
  
  setPaymentStrategy(strategy) {
    this.paymentStrategy = strategy;
  }
  
  getTotal() {
    return this.items.reduce((sum, item) => sum + item.price, 0);
  }
  
  checkout() {
    if (!this.paymentStrategy) {
      throw new Error('请选择支付方式');
    }
    this.paymentStrategy.pay(this.getTotal());
    this.items = [];
  }
}

// 使用
const cart = new ShoppingCart();
cart.addItem(new Product('商品 A', 100));
cart.addItem(new Product('商品 B', 200));

cart.setPaymentStrategy(new AlipayPayment());
cart.checkout(); // 支付宝支付 300 元

效果

  • 购物车全局唯一,避免状态不一致
  • 支付方式可灵活切换,无需修改购物车代码
  • 新增支付方式只需扩展 PaymentStrategy,符合开闭原则

要点

  • 单例模式确保购物车状态唯一
  • 策略模式实现支付方式灵活切换
  • 依赖倒置让系统更易扩展

总结

设计模式是开发者工具箱中的重要武器,但使用时需要注意:

  1. 理解问题本质 - 先理解要解决的问题,再选择合适的模式
  2. 不要过度设计 - 简单问题用简单方案
  3. 结合语言特性 - 利用 JavaScript 的特性简化实现
  4. 保持代码可读 - 模式应该让代码更清晰

掌握这些模式,能让你在面对复杂问题时游刃有余,写出更优雅、可维护的代码。


参考资料

  1. patterns.dev - JavaScript 设计模式:www.patterns.dev/
  2. 《JavaScript 设计模式》- Addy Osmani
  3. Refactoring.Guru - 设计模式教程:refactoring.guru/design-patt...
  4. ES6 装饰器提案:github.com/tc39/propos...
  5. MDN - Proxy: developer.mozilla.org/zh-CN/docs/...

觉得文章对你有帮助?欢迎点赞收藏,分享给更多需要的朋友!

相关推荐
angerdream2 小时前
最新版vue3+TypeScript开发入门到实战教程之Vue3详解props
javascript·vue.js
~欲买桂花同载酒~3 小时前
项目优化-vite打包优化
前端·javascript·vue.js
kyriewen3 小时前
JavaScript 继承的七种姿势:从“原型链”到“class”的进化史
前端·javascript·ecmascript 6
wangfpp3 小时前
性能优化,请先停手:为什么我劝你别上来就搞优化?
前端·javascript·面试
三旬84 小时前
Day.js 源码深度剖析:极简时间库的设计艺术
javascript
清风徐来QCQ4 小时前
js中的模板字符串
开发语言·前端·javascript
SuperEugene5 小时前
Vue3 + Element Plus 表格实战:批量操作、行内编辑、跨页选中逻辑统一|表单与表格规范篇
开发语言·前端·javascript
极梦网络无忧5 小时前
基于 Vite + Vue3 的组件自动注册功能
前端·javascript·vue.js
软弹6 小时前
深入理解 React Ref 机制:useRef 与 forwardRef 的协作原理
前端·javascript·react.js