前端常用设计模式大全

2 阅读6分钟

一、创建型模式

1.1 单例模式(Singleton)

确保一个类只有一个实例,并提供一个全局访问点。

// 1. 使用闭包实现单例
const Singleton = (function () {
  let instance = null;

  function createInstance() {
    return {
      name: '单例实例',
      time: Date.now()
    };
  }

  return {
    getInstance: function () {
      if (!instance) {
        instance = createInstance();
      }
      return instance;
    }
  };
})();

// 2. 使用 ES6 Class 实现单例
class SingletonClass {
  constructor() {
    // 如果已经存在实例,直接返回
    if (SingletonClass.instance) {
      return SingletonClass.instance;
    }
    this.name = '单例实例';
    this.time = Date.now();
    SingletonClass.instance = this;
    // 禁止外部通过 new 创建多个实例
    return this;
  }

  // 静态方法获取实例(可选)
  static getInstance() {
    if (!SingletonClass.instance) {
      SingletonClass.instance = new SingletonClass();
    }
    return SingletonClass.instance;
  }
}

// 使用示例
const s1 = Singleton.getInstance();
const s2 = Singleton.getInstance();
console.log(s1 === s2); // true

const s3 = new SingletonClass();
const s4 = new SingletonClass();
console.log(s3 === s4); // true

应用场景:全局状态管理(Vuex/Redux)、日志记录器、数据库连接池。


1.2 工厂模式(Factory)

定义一个创建对象的接口,由子类决定实例化哪个类。

// 简单工厂
class Button {
  render() {
    throw new Error('子类必须实现 render 方法');
  }
}

class PrimaryButton extends Button {
  render() {
    return '<button class="btn-primary">主要按钮</button>';
  }
}

class DangerButton extends Button {
  render() {
    return '<button class="btn-danger">危险按钮</button>';
  }
}

class ButtonFactory {
  static createButton(type) {
    switch (type) {
      case 'primary':
        return new PrimaryButton();
      case 'danger':
        return new DangerButton();
      default:
        throw new Error('未知按钮类型');
    }
  }
}

// 使用示例
const btn1 = ButtonFactory.createButton('primary');
console.log(btn1.render()); // <button class="btn-primary">主要按钮</button>

const btn2 = ButtonFactory.createButton('danger');
console.log(btn2.render()); // <button class="btn-danger">危险按钮</button>

应用场景:UI 组件库根据类型创建不同组件、解析不同格式的数据(JSON/XML)。


1.3 建造者模式(Builder)

将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示。

class Pizza {
  constructor() {
    this.size = null;
    this.cheese = false;
    this.pepperoni = false;
    this.mushrooms = false;
  }

  describe() {
    return `披萨尺寸: ${this.size}, 奶酪: ${this.cheese}, 意大利辣香肠: ${this.pepperoni}, 蘑菇: ${this.mushrooms}`;
  }
}

class PizzaBuilder {
  constructor() {
    this.pizza = new Pizza();
  }

  setSize(size) {
    this.pizza.size = size;
    return this; // 支持链式调用
  }

  addCheese() {
    this.pizza.cheese = true;
    return this;
  }

  addPepperoni() {
    this.pizza.pepperoni = true;
    return this;
  }

  addMushrooms() {
    this.pizza.mushrooms = true;
    return this;
  }

  build() {
    return this.pizza;
  }
}

// 使用示例
const pizza = new PizzaBuilder()
  .setSize('大')
  .addCheese()
  .addPepperoni()
  .build();

console.log(pizza.describe()); // 披萨尺寸: 大, 奶酪: true, 意大利辣香肠: true, 蘑菇: false

应用场景:构建复杂的配置对象、生成不同风格的报表、组装复杂的 DOM 结构。


二、结构型模式

2.1 适配器模式(Adapter)

将一个类的接口转换成客户希望的另一个接口,使原本不兼容的类可以一起工作。

// 旧版 API(不兼容的接口)
class OldApi {
  fetchData() {
    return { code: 200, data: { name: '旧数据', age: 25 } };
  }
}

// 新版 API 期望的接口
class NewApi {
  getData() {
    return { status: 'success', payload: { name: '新数据', age: 30 } };
  }
}

// 适配器:将旧 API 适配为新 API 的接口
class ApiAdapter {
  constructor(oldApi) {
    this.oldApi = oldApi;
  }

  getData() {
    const result = this.oldApi.fetchData();
    // 转换数据格式
    return {
      status: result.code === 200 ? 'success' : 'error',
      payload: result.data
    };
  }
}

// 使用示例
const oldApi = new OldApi();
const adapter = new ApiAdapter(oldApi);
console.log(adapter.getData()); // { status: 'success', payload: { name: '旧数据', age: 25 } }

// 客户端代码无需修改,统一使用 getData()
const newApi = new NewApi();
console.log(newApi.getData()); // { status: 'success', payload: { name: '新数据', age: 30 } }

应用场景:兼容旧版接口、封装第三方库、统一不同数据源的格式。


2.2 装饰器模式(Decorator)

动态地给对象添加额外的职责,比继承更灵活。

// 基础咖啡类
class Coffee {
  cost() {
    return 10;
  }

  description() {
    return '基础咖啡';
  }
}

// 装饰器基类(也可以直接用函数实现)
class CoffeeDecorator {
  constructor(coffee) {
    this.coffee = coffee;
  }

  cost() {
    return this.coffee.cost();
  }

  description() {
    return this.coffee.description();
  }
}

// 具体装饰器:加牛奶
class MilkDecorator extends CoffeeDecorator {
  cost() {
    return this.coffee.cost() + 5;
  }

  description() {
    return this.coffee.description() + ' + 牛奶';
  }
}

// 具体装饰器:加糖
class SugarDecorator extends CoffeeDecorator {
  cost() {
    return this.coffee.cost() + 3;
  }

  description() {
    return this.coffee.description() + ' + 糖';
  }
}

// 使用示例(组合装饰)
let coffee = new Coffee();
coffee = new MilkDecorator(coffee);
coffee = new SugarDecorator(coffee);
console.log(coffee.description()); // 基础咖啡 + 牛奶 + 糖
console.log(coffee.cost()); // 18

// 更简洁的函数式装饰器
function withMilk(fn) {
  return function (...args) {
    console.log('加了牛奶');
    return fn(...args);
  };
}

function withSugar(fn) {
  return function (...args) {
    console.log('加了糖');
    return fn(...args);
  };
}

const makeCoffee = () => '制作咖啡';
const decorated = withMilk(withSugar(makeCoffee));
console.log(decorated()); // 加了牛奶 加了糖 制作咖啡

应用场景:日志记录、性能监控、权限校验、数据缓存(AOP 编程)。


2.3 代理模式(Proxy)

为其他对象提供一种代理以控制对这个对象的访问。

// 1. 使用 ES6 Proxy 实现代理
const target = {
  name: '敏感数据',
  password: '123456'
};

const handler = {
  get(obj, prop) {
    if (prop === 'password') {
      return '*** 无权访问 ***';
    }
    return obj[prop];
  },
  set(obj, prop, value) {
    if (prop === 'password') {
      console.log('不能直接修改密码');
      return false;
    }
    obj[prop] = value;
    return true;
  }
};

const proxy = new Proxy(target, handler);
console.log(proxy.name); // 敏感数据
console.log(proxy.password); // *** 无权访问 ***
proxy.password = 'new'; // 不能直接修改密码

// 2. 虚拟代理(图片懒加载)
class ImageLoader {
  constructor(src) {
    this.src = src;
  }

  display() {
    console.log(`加载图片: ${this.src}`);
  }
}

class ProxyImage {
  constructor(src) {
    this.src = src;
    this.realImage = null;
  }

  display() {
    if (!this.realImage) {
      console.log('显示占位图...');
      this.realImage = new ImageLoader(this.src);
    }
    this.realImage.display();
  }
}

// 使用示例
const img = new ProxyImage('photo.jpg');
img.display(); // 显示占位图... 加载图片: photo.jpg
img.display(); // 加载图片: photo.jpg(已缓存)

应用场景:数据校验、访问控制、缓存代理、图片懒加载、防抖节流。


三、行为型模式

3.1 观察者模式(Observer)

定义对象间的一对多依赖关系,当一个对象状态改变时,所有依赖它的对象都会得到通知。

// 1. 简单观察者模式
class Subject {
  constructor() {
    this.observers = [];
  }

  // 添加观察者
  attach(observer) {
    this.observers.push(observer);
  }

  // 移除观察者
  detach(observer) {
    const index = this.observers.indexOf(observer);
    if (index > -1) {
      this.observers.splice(index, 1);
    }
  }

  // 通知所有观察者
  notify(data) {
    this.observers.forEach(observer => observer.update(data));
  }
}

class Observer {
  constructor(name) {
    this.name = name;
  }

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

// 使用示例
const subject = new Subject();
const obs1 = new Observer('观察者1');
const obs2 = new Observer('观察者2');

subject.attach(obs1);
subject.attach(obs2);
subject.notify('状态已更新');
// 观察者1 收到通知: 状态已更新
// 观察者2 收到通知: 状态已更新

// 2. Vue 响应式原理的简化版(发布-订阅)
class Dep {
  constructor() {
    this.subscribers = [];
  }

  depend() {
    if (Dep.target && !this.subscribers.includes(Dep.target)) {
      this.subscribers.push(Dep.target);
    }
  }

  notify() {
    this.subscribers.forEach(sub => sub());
  }
}

// 使用示例
Dep.target = null;

function defineReactive(obj, key, val) {
  const dep = new Dep();
  Object.defineProperty(obj, key, {
    get() {
      dep.depend();
      return val;
    },
    set(newVal) {
      if (newVal !== val) {
        val = newVal;
        dep.notify();
      }
    }
  });
}

const data = {};
defineReactive(data, 'count', 0);

// 模拟 watcher
Dep.target = () => console.log('count 变化了:', data.count);
data.count; // 触发依赖收集
Dep.target = null;

data.count = 1; // count 变化了: 1

应用场景:事件系统(EventEmitter)、Vue 响应式、消息队列、DOM 事件监听。


3.2 策略模式(Strategy)

定义一系列算法,将每个算法封装起来,并使它们可以相互替换。

// 策略接口(使用对象存储不同策略)
const strategies = {
  // 普通会员
  normal: (price) => price,
  // 会员折扣
  member: (price) => price * 0.9,
  // VIP 折扣
  vip: (price) => price * 0.8,
  // 超级 VIP
  superVip: (price) => price * 0.7
};

// 上下文:根据策略计算价格
class PriceCalculator {
  constructor(strategy) {
    this.strategy = strategy;
  }

  setStrategy(strategy) {
    this.strategy = strategy;
  }

  calculate(price) {
    return this.strategy(price);
  }
}

// 使用示例
const calculator = new PriceCalculator(strategies.normal);
console.log(calculator.calculate(100)); // 100

calculator.setStrategy(strategies.vip);
console.log(calculator.calculate(100)); // 80

calculator.setStrategy(strategies.superVip);
console.log(calculator.calculate(100)); // 70

// 直接使用策略函数(更简洁)
function getPrice(price, type) {
  return strategies[type] ? strategies[type](price) : price;
}

console.log(getPrice(100, 'member')); // 90
console.log(getPrice(100, 'vip')); // 80

应用场景:表单验证(不同校验规则)、支付方式选择、排序算法切换、动画缓动函数。


3.3 迭代器模式(Iterator)

提供一种方法顺序访问聚合对象中的各个元素,而不暴露其内部表示。

// 1. 实现自定义迭代器
class MyArray {
  constructor(items) {
    this.items = items;
  }

  // 实现 Symbol.iterator 方法
  [Symbol.iterator]() {
    let index = 0;
    const items = this.items;

    return {
      next() {
        if (index < items.length) {
          return { value: items[index++], done: false };
        }
        return { value: undefined, done: true };
      }
    };
  }
}

// 使用示例
const arr = new MyArray([1, 2, 3, 4, 5]);
for (const item of arr) {
  console.log(item); // 1, 2, 3, 4, 5
}

// 2. 实现树结构的迭代器(深度优先遍历)
class TreeNode {
  constructor(value) {
    this.value = value;
    this.children = [];
  }

  addChild(node) {
    this.children.push(node);
    return this;
  }

  // 深度优先迭代器
  *[Symbol.iterator]() {
    yield this.value;
    for (const child of this.children) {
      yield* child;
    }
  }
}

// 使用示例
const root = new TreeNode(1);
root.addChild(new TreeNode(2)).addChild(new TreeNode(3));
root.children[0].addChild(new TreeNode(4)).addChild(new TreeNode(5));

for (const val of root) {
  console.log(val); // 1, 2, 4, 5, 3
}

应用场景:遍历树形结构、数据流处理、自定义集合遍历。


3.4 职责链模式(Chain of Responsibility)

使多个对象都有机会处理请求,将这些对象连成一条链,并沿着这条链传递请求。

// 审批流程示例
class Handler {
  constructor() {
    this.nextHandler = null;
  }

  setNext(handler) {
    this.nextHandler = handler;
    return handler; // 支持链式调用
  }

  handle(request) {
    if (this.nextHandler) {
      return this.nextHandler.handle(request);
    }
    return '请求未被处理';
  }
}

// 具体处理者:经理(可审批 1000 元以下)
class Manager extends Handler {
  handle(request) {
    if (request.amount <= 1000) {
      return `经理审批通过: ${request.amount}元`;
    }
    return super.handle(request);
  }
}

// 具体处理者:总监(可审批 5000 元以下)
class Director extends Handler {
  handle(request) {
    if (request.amount <= 5000) {
      return `总监审批通过: ${request.amount}元`;
    }
    return super.handle(request);
  }
}

// 具体处理者:总经理(可审批 10000 元以下)
class GeneralManager extends Handler {
  handle(request) {
    if (request.amount <= 10000) {
      return `总经理审批通过: ${request.amount}元`;
    }
    return '金额过大,需要董事会审批';
  }
}

// 使用示例
const manager = new Manager();
const director = new Director();
const gm = new GeneralManager();

manager.setNext(director).setNext(gm);

console.log(manager.handle({ amount: 500 })); // 经理审批通过: 500元
console.log(manager.handle({ amount: 2000 })); // 总监审批通过: 2000元
console.log(manager.handle({ amount: 8000 })); // 总经理审批通过: 8000元
console.log(manager.handle({ amount: 20000 })); // 金额过大,需要董事会审批

// 更简洁的函数式版本(中间件模式)
function createMiddlewareChain(...middlewares) {
  return function (context, next) {
    let index = 0;

    function dispatch(i) {
      if (i === middlewares.length) {
        return next ? next(context) : context;
      }
      const middleware = middlewares[i];
      return middleware(context, () => dispatch(i + 1));
    }

    return dispatch(0);
  };
}

// 使用示例:Koa 风格中间件
const chain = createMiddlewareChain(
  (ctx, next) => { console.log('中间件1 开始'); next(); console.log('中间件1 结束'); },
  (ctx, next) => { console.log('中间件2 开始'); next(); console.log('中间件2 结束'); },
  (ctx, next) => { console.log('中间件3 执行'); return ctx; }
);

chain({});
// 中间件1 开始
// 中间件2 开始
// 中间件3 执行
// 中间件2 结束
// 中间件1 结束

应用场景:审批流程、中间件(Express/Koa)、表单校验链、事件冒泡。


3.5 发布-订阅模式(Publish-Subscribe)

观察者模式的升级版,通过事件中心解耦发布者和订阅者。

class EventBus {
  constructor() {
    // 存储事件和对应的回调
    this.events = {};
  }

  // 订阅事件
  on(eventName, callback) {
    if (!this.events[eventName]) {
      this.events[eventName] = [];
    }
    this.events[eventName].push(callback);
    // 返回取消订阅函数
    return () => this.off(eventName, callback);
  }

  // 订阅一次
  once(eventName, callback) {
    const wrapper = (...args) => {
      callback(...args);
      this.off(eventName, wrapper);
    };
    return this.on(eventName, wrapper);
  }

  // 取消订阅
  off(eventName, callback) {
    if (!this.events[eventName]) return;
    this.events[eventName] = this.events[eventName]
      .filter(cb => cb !== callback);
  }

  // 发布事件
  emit(eventName, ...args) {
    if (!this.events[eventName]) return;
    this.events[eventName].forEach(callback => {
      try {
        callback(...args);
      } catch (error) {
        console.error(`事件 ${eventName} 执行错误:`, error);
      }
    });
  }

  // 清除所有事件
  clear() {
    this.events = {};
  }
}

// 使用示例
const bus = new EventBus();

// 订阅事件
const unsubscribe1 = bus.on('userLogin', (user) => {
  console.log(`用户 ${user.name} 登录了`);
});

const unsubscribe2 = bus.on('userLogin', (user) => {
  console.log(`记录登录日志: ${user.name}`);
});

// 发布事件
bus.emit('userLogin', { name: 'Alice' });
// 用户 Alice 登录了
// 记录登录日志: Alice

// 取消订阅
unsubscribe1();
bus.emit('userLogin', { name: 'Bob' });
// 记录登录日志: Bob

// 一次性订阅
bus.once('onceEvent', () => console.log('只执行一次'));
bus.emit('onceEvent'); // 只执行一次
bus.emit('onceEvent'); // 不执行

应用场景:跨组件通信(EventBus)、微前端通信、WebSocket 消息分发、全局状态管理。


四、总结

模式类型模式名称一句话总结常见场景
创建型单例模式全局唯一实例全局状态、日志
创建型工厂模式统一创建对象组件工厂、解析器
创建型建造者模式分步构建复杂对象配置对象、表单生成
结构型适配器模式接口转换兼容API 适配、库封装
结构型装饰器模式动态增强功能日志、缓存、权限
结构型代理模式控制对象访问懒加载、校验、缓存
行为型观察者模式状态变更通知响应式、事件系统
行为型策略模式算法可互换表单校验、支付
行为型迭代器模式统一遍历方式集合遍历、树遍历
行为型职责链模式请求链式处理中间件、审批流程
行为型发布-订阅模式事件中心解耦跨组件通信、消息

掌握这些设计模式,不仅能让你的代码更加优雅、可维护,还能在面试中脱颖而出。建议结合项目实践,逐步理解每种模式的适用场景,而不是生搬硬套。