[特殊字符] JavaScript 设计模式完全指南:从入门到精通(含20种模式)

🧩 JavaScript 设计模式完全指南:从入门到精通(含20种模式)

前言:设计模式不是什么高深莫测的东西,它就是前人踩了无数坑之后总结出来的"套路"。就像做菜有菜谱,打篮球有战术,写代码也有设计模式。掌握它们,你的代码会变得更优雅、更好维护、更容易扩展。

本文用 生活化比喻 + 实际代码 + 源码分析 的方式讲解 20 种 常用的 JavaScript 设计模式,涵盖 创建型、结构型、行为型 三大类,适合有一定 JS 基础的开发者阅读。每种模式均配有 ES5 + ES6 两种写法框架实战案例


📖 目录

  • 一、创建型模式(5种)
    • [1. 单例模式(Singleton)](#1. 单例模式(Singleton))
    • [2. 工厂模式(Factory)](#2. 工厂模式(Factory))
    • [3. 抽象工厂模式(Abstract Factory)](#3. 抽象工厂模式(Abstract Factory))
    • [4. 建造者模式(Builder)](#4. 建造者模式(Builder))
    • [5. 原型模式(Prototype)](#5. 原型模式(Prototype))
  • 二、结构型模式(7种)
    • [6. 适配器模式(Adapter)](#6. 适配器模式(Adapter))
    • [7. 装饰器模式(Decorator)](#7. 装饰器模式(Decorator))
    • [8. 代理模式(Proxy)](#8. 代理模式(Proxy))
    • [9. 外观模式(Facade)](#9. 外观模式(Facade))
    • [10. 桥接模式(Bridge)](#10. 桥接模式(Bridge))
    • [11. 组合模式(Composite)](#11. 组合模式(Composite))
    • [12. 享元模式(Flyweight)](#12. 享元模式(Flyweight))
  • 三、行为型模式(8种)
    • [13. 观察者模式(Observer)](#13. 观察者模式(Observer))
    • [14. 发布-订阅模式(Pub/Sub)](#14. 发布-订阅模式(Pub/Sub))
    • [15. 策略模式(Strategy)](#15. 策略模式(Strategy))
    • [16. 迭代器模式(Iterator)](#16. 迭代器模式(Iterator))
    • [17. 模板方法模式(Template Method)](#17. 模板方法模式(Template Method))
    • [18. 命令模式(Command)](#18. 命令模式(Command))
    • [19. 职责链模式(Chain of Responsibility)](#19. 职责链模式(Chain of Responsibility))
    • [20. 状态模式(State)](#20. 状态模式(State))
  • 四、设计模式之间的关系
  • 五、总结速查表
  • 六、学习路线图

一、创建型模式

这类模式关注的是 "怎么创建对象",帮你把 new 的过程封装起来,让对象的创建更灵活、更可控。


1. 单例模式(Singleton)

🎯 一句话理解

一个类只能创建 一个实例,就像一个国家只有一个总统,一个学校只有一个校长。

🏠 生活类比

你家只有一个 WiFi 路由器,不管手机、电脑、平板怎么连接,都是连的同一个。不管你调用多少次 getInstance(),拿到的都是同一个对象。

💻 代码实现

ES5 写法:

javascript 复制代码
var Singleton = (function () {
  var instance = null;

  function createInstance(config) {
    return {
      name: config.name,
      getTime: function () {
        return new Date().toLocaleString();
      }
    };
  }

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

var a = Singleton.getInstance({ name: '数据库连接' });
var b = Singleton.getInstance({ name: '另一个连接' });
console.log(a === b); // true ✅ 确实是同一个实例
console.log(a.name);  // 数据库连接(第一次的配置)

ES6 写法:

javascript 复制代码
class Singleton {
  static #instance = null;

  constructor(config) {
    if (Singleton.#instance) {
      return Singleton.#instance;
    }
    this.config = config;
    this.createdAt = Date.now();
    Singleton.#instance = this;
  }
}

const a = new Singleton({ db: 'MySQL' });
const b = new Singleton({ db: 'PostgreSQL' }); // 无效,返回的仍然是a
console.log(a === b); // true
console.log(a.config.db); // MySQL

🛠️ 实际应用

1. Vuex / Redux 的 Store:整个应用只有一个数据仓库。

javascript 复制代码
// Vue 项目中的全局弹窗管理器
class ModalManager {
  static #instance = null;
  #currentModal = null;

  constructor() {
    if (ModalManager.#instance) return ModalManager.#instance;
    ModalManager.#instance = this;
  }

  open(component, props = {}) {
    if (this.#currentModal) {
      console.warn('已有弹窗打开,先关闭');
      this.close();
    }
    this.#currentModal = { component, props };
    console.log(`📦 弹窗打开: ${component.name || component}`);
  }

  close() {
    if (this.#currentModal) {
      console.log(`📦 弹窗关闭`);
      this.#currentModal = null;
    }
  }

  get isOpen() {
    return this.#currentModal !== null;
  }
}

// 不管在哪个组件里 new,拿到的都是同一个管理器
const m1 = new ModalManager();
const m2 = new ModalManager();
console.log(m1 === m2); // true

2. 数据库连接池 / WebSocket 连接:

javascript 复制代码
class WebSocketManager {
  static #instance = null;
  #ws = null;
  #listeners = [];

  constructor(url) {
    if (WebSocketManager.#instance) return WebSocketManager.#instance;
    this.#ws = new WebSocket(url);
    this.#ws.onmessage = (e) => {
      this.#listeners.forEach(cb => cb(e.data));
    };
    WebSocketManager.#instance = this;
  }

  onMessage(cb) { this.#listeners.push(cb); }
  send(data) { this.#ws.send(JSON.stringify(data)); }
}

⚠️ 注意事项

  • 单例模式容易被滥用,只有真正需要全局唯一的场景才用
  • 在测试中单例可能导致状态污染,需要注意 reset
  • 多线程环境下需要加锁(JS 单线程无此问题)

2. 工厂模式(Factory)

🎯 一句话理解

你不需要知道产品怎么制造的,只需要告诉工厂 "我要什么",工厂帮你造出来。

🏠 生活类比

去餐厅点餐,你说"来份宫保鸡丁",后厨怎么做你不管,做好端上来就行。你就是调用者,后厨就是工厂。

💻 代码实现

简单工厂(Simple Factory):

javascript 复制代码
class Dog {
  constructor(name) {
    this.name = name;
    this.type = '狗';
    this.sound = '汪汪!';
  }
  speak() { return `${this.name}: ${this.sound}`; }
  ability() { return `${this.name}会看家`; }
}

class Cat {
  constructor(name) {
    this.name = name;
    this.type = '猫';
    this.sound = '喵喵~';
  }
  speak() { return `${this.name}: ${this.sound}`; }
  ability() { return `${this.name}会抓老鼠`; }
}

class Parrot {
  constructor(name) {
    this.name = name;
    this.type = '鹦鹉';
    this.sound = '你好!';
  }
  speak() { return `${this.name}: ${this.sound}`; }
  ability() { return `${this.name}会学说话`; }
}

// 简单工厂函数 ------ 你只需要传入类型
function AnimalFactory(type, name) {
  const animalMap = {
    dog: Dog,
    cat: Cat,
    parrot: Parrot,
  };
  const AnimalClass = animalMap[type];
  if (!AnimalClass) throw new Error(`不支持的动物类型: ${type}`);
  return new AnimalClass(name);
}

// 使用
const dog = AnimalFactory('dog', '旺财');
const cat = AnimalFactory('cat', '咪咪');
const parrot = AnimalFactory('parrot', '波利');

console.log(dog.speak());    // 旺财: 汪汪!
console.log(cat.ability());  // 咪咪会抓老鼠
console.log(parrot.speak()); // 波利: 你好!

工厂方法模式(Factory Method):

javascript 复制代码
// 当简单工厂不够灵活时,让子类决定创建哪个对象

class AnimalFactory {
  createAnimal(name) {
    throw new Error('子类必须实现 createAnimal 方法');
  }
}

class DogFactory extends AnimalFactory {
  createAnimal(name) {
    return new Dog(name);
  }
}

class CatFactory extends AnimalFactory {
  createAnimal(name) {
    return new Cat(name);
  }
}

// 使用
const dogFactory = new DogFactory();
const myDog = dogFactory.createAnimal('来福');
console.log(myDog.speak()); // 来福: 汪汪!

🛠️ 实际应用

React createElement:

javascript 复制代码
// JSX 编译后的工厂函数
const element = React.createElement('div', { className: 'app' }, 'Hello');
// 等价于 JSX: <div className="app">Hello</div>

UI 组件库按钮工厂:

javascript 复制代码
function createButton({ variant, size, text, onClick }) {
  const button = document.createElement('button');

  const variantStyles = {
    primary: { backgroundColor: '#1890ff', color: '#fff' },
    danger:  { backgroundColor: '#ff4d4f', color: '#fff' },
    ghost:   { backgroundColor: 'transparent', color: '#1890ff', border: '1px solid #1890ff' },
    text:    { backgroundColor: 'transparent', color: '#333' },
  };

  const sizeStyles = {
    small:  { padding: '4px 12px', fontSize: '12px' },
    medium: { padding: '8px 16px', fontSize: '14px' },
    large:  { padding: '12px 24px', fontSize: '16px' },
  };

  Object.assign(button.style, variantStyles[variant], sizeStyles[size]);
  button.textContent = text;
  button.onclick = onClick;

  return button;
}

// 使用
document.body.appendChild(
  createButton({
    variant: 'primary',
    size: 'large',
    text: '提交',
    onClick: () => console.log('clicked')
  })
);

📊 简单工厂 vs 工厂方法

对比 简单工厂 工厂方法
创建逻辑 集中在一个函数 分散到各子类
扩展性 需要改工厂函数 新建工厂子类
适用场景 产品种类少 产品种类多且频繁扩展

3. 抽象工厂模式(Abstract Factory)

🎯 一句话理解

工厂的工厂。提供一个创建 一系列相关对象 的接口,而不指定具体类。

🏠 生活类比

你要装修房子,需要沙发、茶几、电视柜。你选择"北欧风套餐"或"中式风套餐",一套下来风格统一。不用自己去搭配。

💻 代码实现

javascript 复制代码
// 产品族:按钮和输入框
class WindowsButton {
  render() { return '🪟 Windows 风格按钮'; }
}
class WindowsInput {
  render() { return '🪟 Windows 风格输入框'; }
}
class MacButton {
  render() { return '🍎 Mac 风格按钮'; }
}
class MacInput {
  render() { return '🍎 Mac 风格输入框'; }
}

// 抽象工厂
class UIFactory {
  createButton() { throw new Error('必须实现'); }
  createInput() { throw new Error('必须实现'); }
}

// 具体工厂:Windows
class WindowsFactory extends UIFactory {
  createButton() { return new WindowsButton(); }
  createInput() { return new WindowsInput(); }
}

// 具体工厂:Mac
class MacFactory extends UIFactory {
  createButton() { return new MacButton(); }
  createInput() { return new MacInput(); }
}

// 使用 ------ 根据平台选择工厂
function createUI(platform) {
  const factoryMap = { windows: WindowsFactory, mac: MacFactory };
  const Factory = factoryMap[platform];
  const factory = new Factory();

  return {
    button: factory.createButton(),
    input: factory.createInput()
  };
}

const ui = createUI('mac');
console.log(ui.button.render()); // 🍎 Mac 风格按钮
console.log(ui.input.render());  // 🍎 Mac 风格输入框

📊 工厂模式三兄弟对比

模式 特点 类比
简单工厂 一个函数搞定所有 餐厅点菜
工厂方法 每个产品有自己的工厂 不同窗口取不同菜品
抽象工厂 一系列产品的统一工厂 整套装修方案

4. 建造者模式(Builder)

🎯 一句话理解

把一个 复杂的对象 拆成 一步一步来构建,像搭积木一样,每一步都可以自定义。

🏠 生活类比

你去电脑城组装电脑:先选 CPU,再选显卡,再选内存,最后选机箱。每一步你都可以自由搭配,最后组装成一台完整的电脑。

💻 代码实现

javascript 复制代码
// 产品
class Computer {
  constructor() {
    this.parts = [];
  }
  add(part) {
    this.parts.push(part);
    return this;
  }
  show() {
    console.log('💻 电脑配置清单:');
    this.parts.forEach(p => console.log(`  - ${p}`));
    console.log(`💰 总价: ¥${this.calculatePrice()}`);
  }
  calculatePrice() {
    const priceMap = {
      'Intel i7-13700K': 2500, 'AMD Ryzen 9 7950X': 3500,
      'RTX 4070': 4000, 'RTX 4090': 12000,
      '32GB DDR5': 600, '64GB DDR5': 1200,
      '1TB NVMe SSD': 500, '2TB NVMe SSD': 900,
    };
    return this.parts.reduce((sum, p) => sum + (priceMap[p] || 0), 0);
  }
}

// 建造者
class ComputerBuilder {
  #computer;

  constructor() {
    this.#computer = new Computer();
  }

  addCPU(cpu) {
    this.#computer.add(`CPU: ${cpu}`);
    return this; // 链式调用
  }

  addGPU(gpu) {
    this.#computer.add(`显卡: ${gpu}`);
    return this;
  }

  addRAM(size) {
    this.#computer.add(`内存: ${size}`);
    return this;
  }

  addStorage(storage) {
    this.#computer.add(`硬盘: ${storage}`);
    return this;
  }

  reset() {
    this.#computer = new Computer();
    return this;
  }

  build() {
    const result = this.#computer;
    this.#computer = new Computer();
    return result;
  }
}

// 使用 ------ 链式调用,清晰优雅
const myPC = new ComputerBuilder()
  .addCPU('Intel i7-13700K')
  .addGPU('RTX 4070')
  .addRAM('32GB DDR5')
  .addStorage('1TB NVMe SSD')
  .build();

myPC.show();
// 💻 电脑配置清单:
//   - CPU: Intel i7-13700K
//   - 显卡: RTX 4070
//   - 内存: 32GB DDR5
//   - 硬盘: 1TB NVMe SSD
// 💰 总价: ¥7600

🛠️ 实际应用

SQL 查询构建器:

javascript 复制代码
class QueryBuilder {
  #table = '';
  #fields = ['*'];
  #conditions = [];
  #orderField = '';
  #orderDir = 'ASC';
  #limitVal = 0;

  table(name) { this.#table = name; return this; }
  select(...fields) { this.#fields = fields; return this; }
  where(condition) { this.#conditions.push(condition); return this; }
  orderBy(field, dir = 'ASC') { this.#orderField = field; this.#orderDir = dir; return this; }
  limit(n) { this.#limitVal = n; return this; }

  build() {
    let sql = `SELECT ${this.#fields.join(', ')} FROM ${this.#table}`;
    if (this.#conditions.length) sql += ` WHERE ${this.#conditions.join(' AND ')}`;
    if (this.#orderField) sql += ` ORDER BY ${this.#orderField} ${this.#orderDir}`;
    if (this.#limitVal) sql += ` LIMIT ${this.#limitVal}`;
    return sql;
  }
}

const sql = new QueryBuilder()
  .table('users')
  .select('id', 'name', 'email')
  .where('age > 18')
  .where('status = "active"')
  .orderBy('created_at', 'DESC')
  .limit(10)
  .build();

console.log(sql);
// SELECT id, name, email FROM users WHERE age > 18 AND status = "active" ORDER BY created_at DESC LIMIT 10

axios 请求构建器:

javascript 复制代码
class RequestBuilder {
  #config = { method: 'GET', headers: {}, timeout: 5000 };

  setMethod(method) { this.#config.method = method; return this; }
  setURL(url) { this.#config.url = url; return this; }
  setHeader(key, value) { this.#config.headers[key] = value; return this; }
  setBody(data) { this.#config.data = data; return this; }
  setTimeout(ms) { this.#config.timeout = ms; return this; }

  async execute() {
    return axios(this.#config);
  }
}

// 使用
const response = await new RequestBuilder()
  .setMethod('POST')
  .setURL('/api/login')
  .setHeader('Content-Type', 'application/json')
  .setBody({ username: 'admin', password: '123' })
  .setTimeout(10000)
  .execute();

5. 原型模式(Prototype)

🎯 一句话理解

通过 克隆 一个已有对象来创建新对象,而不是从头 new。利用 JS 原型链的特性,这是最自然的模式。

🏠 生活类比

就像复印文件------你有一份模板,直接复印就行,不用重新打字。或者像细胞分裂,一个变两个,两个变四个。

💻 代码实现

javascript 复制代码
// 深拷贝实现原型克隆
function deepClone(obj, cache = new WeakMap()) {
  if (obj === null || typeof obj !== 'object') return obj;
  if (cache.has(obj)) return cache.get(obj);
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
  if (Array.isArray(obj)) return obj.map(item => deepClone(item, cache));

  const clone = Object.create(Object.getPrototypeOf(obj));
  cache.set(obj, clone);
  for (const key of Reflect.ownKeys(obj)) {
    clone[key] = deepClone(obj[key], cache);
  }
  return clone;
}

// 使用
const templateUser = {
  name: '模板用户',
  role: 'user',
  permissions: ['read', 'write'],
  settings: { theme: 'dark', lang: 'zh' }
};

const user1 = deepClone(templateUser);
user1.name = '张三';
user1.settings.theme = 'light';

const user2 = deepClone(templateUser);
user2.name = '李四';
user2.permissions.push('delete');

console.log(user1.settings.theme);    // light(互不影响)
console.log(user2.settings.theme);    // dark(保持原始值)
console.log(templateUser.settings.theme); // dark(模板不变)
javascript 复制代码
// Object.create 实现原型继承
const animalPrototype = {
  type: '未知',
  speak() { return `${this.name}: ${this.sound}(我是${this.type})`; },
  clone() { return Object.create(this); }
};

const dog = animalPrototype.clone();
dog.name = '旺财'; dog.type = '狗'; dog.sound = '汪汪!';

console.log(dog.speak()); // 旺财: 汪汪!(我是狗)

🛠️ 实际应用

JS 的整个原型继承体系就是原型模式的实现。Object.create()__proto__class extends 底层都是原型链操作。

javascript 复制代码
// Vue 2 中的 VNode 克隆
function cloneVNode(vnode) {
  const cloned = new VNode(
    vnode.tag, vnode.data, vnode.children,
    vnode.text, vnode.elm, vnode.context,
    vnode.componentOptions
  );
  return cloned;
}

二、结构型模式

这类模式关注的是 "怎么把对象组合起来",帮你灵活地搭建代码结构,让不同模块协同工作。


6. 适配器模式(Adapter)

🎯 一句话理解

不兼容的接口 能够协同工作,就像万能转换插头。

🏠 生活类比

你去国外旅游,带的中国电器插头是扁的,但当地插座是圆的。怎么办?买个转换插头就行了。这个转换插头就是适配器。

💻 代码实现

javascript 复制代码
// 场景:对接第三方地图 SDK

// 高德地图接口
const gaodeMap = {
  show(lat, lng) { console.log(`高德地图: (${lat}, ${lng})`); },
  getRoute(from, to) { return `高德路线: ${from} → ${to}`; }
};

// 百度地图接口(参数格式不同)
const baiduMap = {
  display(coordinates) { console.log(`百度地图: ${JSON.stringify(coordinates)}`); },
  navigation(start, end) { return `百度路线: ${start} → ${end}`; }
};

// 统一接口适配器
class MapAdapter {
  constructor(mapSDK) { this.map = mapSDK; }

  showPoint(lat, lng) {
    if (this.map.show) this.map.show(lat, lng);
    else if (this.map.display) this.map.display({ lat, lng });
  }

  getRoute(from, to) {
    if (this.map.getRoute) return this.map.getRoute(from, to);
    else if (this.map.navigation) return this.map.navigation(from, to);
  }
}

// 使用 ------ 统一接口,不关心底层是哪个地图
const adapter1 = new MapAdapter(gaodeMap);
const adapter2 = new MapAdapter(baiduMap);

adapter1.showPoint(39.9, 116.3); // 高德地图: (39.9, 116.3)
adapter2.showPoint(39.9, 116.3); // 百度地图: {"lat":39.9,"lng":116.3}

🛠️ 实际应用

前后端数据格式适配:

javascript 复制代码
const apiResponse = {
  order_id: 1001,
  create_time: '2026-05-18T10:30:00Z',
  total_amount: 299.9,
  pay_status: 1,
  user_info: { u_name: '张三', u_phone: '13800138000' }
};

function OrderAdapter(data) {
  const payStatusMap = { 0: '未支付', 1: '已支付', 2: '已取消', 3: '已退款' };
  return {
    orderId: data.order_id,
    createTime: new Date(data.create_time).toLocaleString('zh-CN'),
    totalAmount: `¥${data.total_amount.toFixed(2)}`,
    payStatus: payStatusMap[data.pay_status],
    userName: data.user_info.u_name,
    userPhone: data.user_info.u_phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
  };
}

const order = OrderAdapter(apiResponse);
console.log(order);
// { orderId: 1001, createTime: '2026/5/18 18:30:00', totalAmount: '¥299.90',
//   payStatus: '已支付', userName: '张三', userPhone: '138****8000' }

7. 装饰器模式(Decorator)

🎯 一句话理解

不修改原对象,给它动态添加新功能,像给手机套壳、贴膜、挂件一样,层层增强。

🏠 生活类比

你买了一部手机,它本身能打电话。你可以给它套个壳(防摔功能)、贴个膜(防刮功能)、挂个手绳(防丢功能)。手机本身没变,但功能一步步增强了。

💻 代码实现

javascript 复制代码
// 基础咖啡
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() + 3; }
  description() { return this.coffee.description() + ' + 🥛牛奶'; }
}

class MochaDecorator extends CoffeeDecorator {
  cost() { return this.coffee.cost() + 5; }
  description() { return this.coffee.description() + ' + 🍫摩卡'; }
}

class WhipDecorator extends CoffeeDecorator {
  cost() { return this.coffee.cost() + 2; }
  description() { return this.coffee.description() + ' + 🫧奶泡'; }
}

// 自由组合 ------ 像搭积木一样
let myCoffee = new Coffee();
myCoffee = new MilkDecorator(myCoffee);
myCoffee = new MochaDecorator(myCoffee);
myCoffee = new WhipDecorator(myCoffee);

console.log(myCoffee.description()); // ☕ 黑咖啡 + 🥛牛奶 + 🍫摩卡 + 🫧奶泡
console.log(`价格: ¥${myCoffee.cost()}`);  // 价格: ¥20

函数装饰器(AOP 风格):

javascript 复制代码
// 通用函数装饰器
function withLogging(fn) {
  return function (...args) {
    console.log(`📞 调用 ${fn.name},参数:`, args);
    const result = fn.apply(this, args);
    console.log(`✅ ${fn.name} 返回:`, result);
    return result;
  };
}

function withTiming(fn) {
  return function (...args) {
    const start = performance.now();
    const result = fn.apply(this, args);
    console.log(`⏱ ${fn.name} 耗时: ${(performance.now() - start).toFixed(2)}ms`);
    return result;
  };
}

function withRetry(fn, maxRetries = 3) {
  return async function (...args) {
    for (let i = 0; i < maxRetries; i++) {
      try {
        return await fn.apply(this, args);
      } catch (err) {
        console.warn(`🔄 第${i + 1}次重试: ${err.message}`);
        if (i === maxRetries - 1) throw err;
        await new Promise(r => setTimeout(r, 1000 * (i + 1)));
      }
    }
  };
}

// 使用 ------ 组合装饰器
async function fetchUserData(userId) {
  const res = await fetch(`/api/user/${userId}`);
  return res.json();
}

const enhancedFetch = withRetry(withLogging(fetchUserData), 3);

🛠️ 实际应用

React 高阶组件(HOC):

javascript 复制代码
function withAuth(WrappedComponent) {
  return function AuthComponent(props) {
    const token = localStorage.getItem('token');
    if (!token) return <Navigate to="/login" />;
    return <WrappedComponent {...props} />;
  };
}

function withLoading(WrappedComponent) {
  return function LoadingComponent({ isLoading, ...props }) {
    if (isLoading) return <Spinner />;
    return <WrappedComponent {...props} />;
  };
}

// 组合使用
const EnhancedPage = withLoading(withAuth(Dashboard));

8. 代理模式(Proxy)

🎯 一句话理解

给对象找个 "代理",在访问对象之前加一层控制。和装饰器的区别是:代理控制访问,装饰器增强功能。

🏠 生活类比

明星的经纪人。你想找明星代言,不是直接联系明星,而是先联系经纪人。经纪人会帮你筛选、谈价格、安排时间。经纪人就是代理。

💻 代码实现

javascript 复制代码
const user = {
  name: '张三',
  age: 25,
  salary: 20000,
  password: 'abc123'
};

const userProxy = new Proxy(user, {
  // 拦截读取
  get(target, key) {
    if (key === 'salary') {
      const isHR = localStorage.getItem('role') === 'hr';
      if (!isHR) {
        console.warn('⚠ 薪资信息需要HR权限');
        return '***';
      }
    }
    if (key === 'password') {
      console.warn('⚠ 密码字段不可读取');
      return undefined;
    }
    return target[key];
  },

  // 拦截赋值
  set(target, key, value) {
    if (key === 'age' && (value < 0 || value > 150)) {
      throw new Error('年龄不合法');
    }
    if (key === 'password' && value.length < 6) {
      throw new Error('密码至少6位');
    }
    console.log(`📝 ${key} 已更新: ${target[key]} → ${value}`);
    target[key] = value;
    return true;
  }
});

console.log(userProxy.name);    // 张三
console.log(userProxy.salary);  // ⚠ 薪资信息需要HR权限 → '***'

缓存代理(性能优化利器):

javascript 复制代码
function createCacheProxy(fn) {
  const cache = new Map();

  return new Proxy(fn, {
    apply(target, thisArg, args) {
      const key = JSON.stringify(args);
      if (cache.has(key)) {
        console.log(`🎯 缓存命中: ${key}`);
        return cache.get(key);
      }
      const result = target.apply(thisArg, args);
      cache.set(key, result);
      return result;
    }
  });
}

// 昂贵的计算函数
const fastFib = createCacheProxy(function fib(n) {
  console.log(`💻 计算中... fib(${n})`);
  if (n <= 1) return n;
  return fastFib(n - 1) + fastFib(n - 2);
});

console.log(fastFib(10)); // 第一次计算
console.log(fastFib(10)); // 🎯 缓存命中

🛠️ 实际应用

Vue 3 的 reactive 实现(简化版):

javascript 复制代码
const targetMap = new WeakMap();
let activeEffect = null;

function reactive(target) {
  return new Proxy(target, {
    get(obj, key) {
      track(obj, key);
      const result = obj[key];
      return typeof result === 'object' ? reactive(result) : result;
    },
    set(obj, key, value) {
      const oldValue = obj[key];
      obj[key] = value;
      if (oldValue !== value) trigger(obj, key);
      return true;
    }
  });
}

📊 代理 vs 装饰器

对比 代理模式 装饰器模式
目的 控制访问 增强功能
是否改变接口 不变 可扩展
创建方式 代理控制对象创建 装饰器包裹已有对象

9. 外观模式(Facade)

🎯 一句话理解

复杂的子系统 封装成一个 简单的接口,对外只暴露一个入口。让调用者不用关心内部复杂性。

🏠 生活类比

你在家看电影,需要:开电视 → 切换HDMI → 开音响 → 调音量 → 关灯 → 拉窗帘。每次都要操作6个遥控器太麻烦。买个"智能家居"面板,一键"观影模式"搞定。这个面板就是外观模式。

💻 代码实现

javascript 复制代码
// 复杂的子系统
class TV {
  on() { console.log('📺 电视打开'); }
  off() { console.log('📺 电视关闭'); }
  setSource(src) { console.log(`📺 切换到 ${src}`); }
}
class SoundSystem {
  on() { console.log('🔊 音响打开'); }
  off() { console.log('🔊 音响关闭'); }
  setVolume(v) { console.log(`🔊 音量: ${v}`); }
}
class Lights {
  setLevel(level) { console.log(`💡 灯光亮度: ${level}%`); }
}
class Curtains {
  open() { console.log('🪟 窗帘打开'); }
  close() { console.log('🪟 窗帘关闭'); }
}

// 外观 ------ 一键操作
class HomeFacade {
  constructor() {
    this.tv = new TV();
    this.sound = new SoundSystem();
    this.lights = new Lights();
    this.curtains = new Curtains();
  }

  watchMovie() {
    console.log('🎬 开启观影模式...');
    this.curtains.close();
    this.lights.setLevel(20);
    this.tv.on();
    this.tv.setSource('HDMI');
    this.sound.on();
    this.sound.setVolume(50);
  }

  endMovie() {
    console.log('🎬 结束观影模式...');
    this.tv.off();
    this.sound.off();
    this.lights.setLevel(100);
    this.curtains.open();
  }

  sleepMode() {
    console.log('🌙 睡眠模式...');
    this.tv.off();
    this.sound.off();
    this.lights.setLevel(0);
    this.curtains.close();
  }
}

// 使用 ------ 简单!
const home = new HomeFacade();
home.watchMovie();
// 🪟 窗帘关闭 → 💡 灯光亮度: 20% → 📺 电视打开 → 📺 切换到 HDMI → 🔊 音响打开 → 🔊 音量: 50

🛠️ 实际应用

项目中的通用 API 模块:

javascript 复制代码
class ApiFacade {
  constructor(baseURL) {
    this.baseURL = baseURL;
    this.token = localStorage.getItem('token');
  }

  async request(endpoint, options = {}) {
    const url = `${this.baseURL}${endpoint}`;
    const headers = {
      'Content-Type': 'application/json',
      ...(this.token ? { Authorization: `Bearer ${this.token}` } : {}),
      ...options.headers
    };

    try {
      const res = await fetch(url, { ...options, headers });
      if (res.status === 401) {
        this.token = null;
        localStorage.removeItem('token');
        window.location.href = '/login';
        return;
      }
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      return res.json();
    } catch (err) {
      console.error(`API Error: ${err.message}`);
      throw err;
    }
  }

  get(endpoint) { return this.request(endpoint); }
  post(endpoint, data) { return this.request(endpoint, { method: 'POST', body: JSON.stringify(data) }); }
  put(endpoint, data) { return this.request(endpoint, { method: 'PUT', body: JSON.stringify(data) }); }
  delete(endpoint) { return this.request(endpoint, { method: 'DELETE' }); }

  login(username, password) {
    return this.post('/auth/login', { username, password }).then(data => {
      this.token = data.token;
      localStorage.setItem('token', data.token);
      return data;
    });
  }
  getUser(id) { return this.get(`/users/${id}`); }
  getArticles(page = 1) { return this.get(`/articles?page=${page}`); }
}

const api = new ApiFacade('https://api.example.com');

10. 桥接模式(Bridge)

🎯 一句话理解

抽象部分实现部分 分离,使它们可以独立变化。避免多层继承导致的类爆炸。

🏠 生活类比

遥控器和电视是分开的。换遥控器不用换电视,换电视不用换遥控器。它们通过红外信号(桥接)通信。

💻 代码实现

javascript 复制代码
// 实现部分:颜色
class Color {
  apply() { throw new Error('必须实现'); }
}
class Red extends Color { apply() { return '红色'; } }
class Blue extends Color { apply() { return '蓝色'; } }
class Green extends Color { apply() { return '绿色'; } }

// 抽象部分:形状
class Shape {
  constructor(color) { this.color = color; }
  draw() { throw new Error('必须实现'); }
}
class Circle extends Shape {
  draw() { console.log(`⭕ 画一个${this.color.apply()}的圆`); }
}
class Square extends Shape {
  draw() { console.log(`⬜ 画一个${this.color.apply()}的正方形`); }
}
class Triangle extends Shape {
  draw() { console.log(`🔺 画一个${this.color.apply()}的三角形`); }
}

// 使用 ------ 形状和颜色自由组合
new Circle(new Red()).draw();      // ⭕ 画一个红色的圆
new Square(new Blue()).draw();     // ⬜ 画一个蓝色的正方形
new Triangle(new Green()).draw();  // 🔺 画一个绿色的三角形

// 新增颜色?加一个类就行,不用改任何形状代码
class Gradient extends Color { apply() { return '渐变色'; } }
new Circle(new Gradient()).draw(); // ⭕ 画一个渐变色的圆

更实际的例子 ------ 消息发送系统:

javascript 复制代码
// 实现部分:发送渠道
class EmailSender {
  send(to, message) { console.log(`📧 发邮件给 ${to}: ${message}`); }
}
class SmsSender {
  send(to, message) { console.log(`📱 发短信给 ${to}: ${message}`); }
}
class WechatSender {
  send(to, message) { console.log(`💬 发微信给 ${to}: ${message}`); }
}

// 抽象部分:消息类型
class Message {
  constructor(sender) { this.sender = sender; }
  deliver(to, content) { throw new Error('必须实现'); }
}
class AlertMessage extends Message {
  deliver(to, content) { this.sender.send(to, `🚨 警报: ${content}`); }
}
class PromoMessage extends Message {
  deliver(to, content) { this.sender.send(to, `🎉 优惠: ${content}`); }
}

// 组合使用 ------ 3种渠道 × 2种类型 = 6种组合,只需5个类
new AlertMessage(new EmailSender()).deliver('admin@test.com', 'CPU占用90%');
new PromoMessage(new SmsSender()).deliver('13800138000', '全场5折');

11. 组合模式(Composite)

🎯 一句话理解

把对象组合成 树形结构 ,让用户对单个对象和组合对象的使用具有 一致性

🏠 生活类比

文件系统:文件夹里面可以放文件,也可以放子文件夹。你不管是双击文件还是双击文件夹,都是"打开"操作。操作系统把文件和文件夹统一对待了。

💻 代码实现

javascript 复制代码
// 统一接口
class FileSystemComponent {
  constructor(name) { this.name = name; }
  display(indent = '') { throw new Error('必须实现'); }
  getSize() { throw new Error('必须实现'); }
}

// 叶子节点:文件
class File extends FileSystemComponent {
  constructor(name, size) { super(name); this.size = size; }
  display(indent = '') { console.log(`${indent}📄 ${this.name} (${this.size}KB)`); }
  getSize() { return this.size; }
}

// 组合节点:文件夹
class Folder extends FileSystemComponent {
  constructor(name) { super(name); this.children = []; }
  add(component) { this.children.push(component); return this; }
  remove(component) { this.children = this.children.filter(c => c !== component); }
  display(indent = '') {
    console.log(`${indent}📁 ${this.name}/`);
    this.children.forEach(child => child.display(indent + '  '));
  }
  getSize() {
    return this.children.reduce((sum, child) => sum + child.getSize(), 0);
  }
}

// 构建文件树
const root = new Folder('项目');
const src = new Folder('src');
const components = new Folder('components');

root.add(src);
src.add(components);
components.add(new File('Header.vue', 5));
components.add(new File('Footer.vue', 3));
root.add(new File('package.json', 1));

root.display();
// 📁 项目/
//   📁 src/
//     📁 components/
//       📄 Header.vue (5KB)
//       📄 Footer.vue (3KB)
//   📄 package.json (1KB)

console.log(`总大小: ${root.getSize()}KB`); // 总大小: 9KB

🛠️ 实际应用

DOM 树结构Vue/React 组件树 都是组合模式的体现。每个组件可以包含子组件,形成树形结构,统一使用相同的接口。


12. 享元模式(Flyweight)

🎯 一句话理解

通过 共享 相同的数据来 减少内存使用,把公共的部分提取出来,只保留一份。

🏠 生活类比

咖啡店有100个客人,每个人都要一杯拿铁。咖啡店不需要准备100份拿铁配方,只需要1份配方 + 100个杯子。配方就是享元(共享的),杯子就是外部状态(每个客人独有的)。

💻 代码实现

javascript 复制代码
// 享元工厂 ------ 共享字体对象
class FontFactory {
  static #fonts = new Map();

  static getFont(family, size, color, bold = false, italic = false) {
    const key = `${family}|${size}|${color}|${bold}|${italic}`;
    if (!this.#fonts.has(key)) {
      this.#fonts.set(key, { family, size, color, bold, italic });
      console.log(`🎨 创建新字体: ${key}`);
    }
    return this.#fonts.get(key);
  }

  static getFontCount() { return this.#fonts.size; }
}

// 字符类 ------ 只存储位置信息,字体共享
class Character {
  constructor(char, x, y, font) {
    this.char = char;   // 外部状态
    this.x = x;
    this.y = y;
    this.font = font;   // 内部状态:共享的!
  }

  render() {
    return `[${this.font.family} ${this.font.size}px] ${this.char} at (${this.x}, ${this.y})`;
  }
}

// 10万个字符,但只有几种字体
const chars = [];
const text = '这是一段示例文本用来演示享元模式';

for (let i = 0; i < 100000; i++) {
  const char = text[i % text.length];
  const font = FontFactory.getFont(
    i % 3 === 0 ? '宋体' : i % 3 === 1 ? '微软雅黑' : 'Arial',
    i % 2 === 0 ? 14 : 16,
    i % 4 === 0 ? '#333' : '#666',
    i % 5 === 0,
    i % 7 === 0
  );
  chars.push(new Character(char, i * 10, 0, font));
}

console.log(`字符数: ${chars.length}`);                  // 100000
console.log(`字体对象数: ${FontFactory.getFontCount()}`); // 远小于10万!
// 不用享元:10万个字体对象
// 用了享元:10万字符共享少量字体对象

列表虚拟化也是享元思想:

javascript 复制代码
class VirtualList {
  constructor(container, items, itemHeight = 40) {
    this.container = container;
    this.items = items;
    this.itemHeight = itemHeight;
    this.visibleCount = Math.ceil(container.clientHeight / itemHeight) + 2;
    this.pool = []; // DOM 节点池(享元)

    for (let i = 0; i < this.visibleCount; i++) {
      const div = document.createElement('div');
      div.style.height = `${itemHeight}px`;
      this.pool.push(div);
      container.appendChild(div);
    }

    container.addEventListener('scroll', () => this.render());
    this.render();
  }

  render() {
    const scrollTop = this.container.scrollTop;
    const startIndex = Math.floor(scrollTop / this.itemHeight);
    this.pool.forEach((div, i) => {
      const index = startIndex + i;
      if (index < this.items.length) {
        div.textContent = this.items[index];
        div.style.transform = `translateY(${index * this.itemHeight - scrollTop}px)`;
      }
    });
  }
}
// 10万条数据,但只创建 ~20 个 DOM 节点

三、行为型模式

这类模式关注的是 "对象之间怎么通信",帮你组织代码的执行流程,让逻辑更清晰、更灵活。


13. 观察者模式(Observer)

🎯 一句话理解

一个对象状态变化时,自动通知 所有依赖它的对象。像班主任通知全班同学一样。

🏠 生活类比

班主任在班级群里发通知:"明天考试!" 所有同学(观察者)都会收到消息。老师不需要一个个私聊,发一次全员可见。

💻 代码实现

javascript 复制代码
// 被观察者(Subject)
class Subject {
  #observers = [];
  #state = null;

  subscribe(observer) {
    if (!this.#observers.includes(observer)) {
      this.#observers.push(observer);
      console.log(`✅ ${observer.name} 已订阅`);
    }
    return this;
  }

  unsubscribe(observer) {
    this.#observers = this.#observers.filter(o => o !== observer);
    console.log(`❌ ${observer.name} 已取消订阅`);
    return this;
  }

  setState(newState) {
    const oldState = this.#state;
    this.#state = newState;
    console.log(`📢 状态更新: ${JSON.stringify(oldState)} → ${JSON.stringify(newState)}`);
    this.#observers.forEach(o => o.update(newState, oldState));
  }
}

// 观察者(Observer)
class Observer {
  constructor(name, callback) {
    this.name = name;
    this.callback = callback;
  }
  update(newState, oldState) {
    this.callback(newState, oldState);
  }
}

// 使用 ------ 气象站通知多个显示屏
const weatherStation = new Subject();

const phoneDisplay = new Observer('手机屏', (state) => {
  console.log(`📱 手机: 温度 ${state.temperature}°C, 湿度 ${state.humidity}%`);
});
const alarmSystem = new Observer('报警系统', (state) => {
  if (state.temperature > 35) console.log(`🚨 高温警报: ${state.temperature}°C!`);
});

weatherStation.subscribe(phoneDisplay).subscribe(alarmSystem);

weatherStation.setState({ temperature: 28, humidity: 65, weather: '晴' });
// 📱 手机: 温度 28°C, 湿度 65%

weatherStation.setState({ temperature: 38, humidity: 40, weather: '热' });
// 📱 手机: 温度 38°C, 湿度 40%
// 🚨 高温警报: 38°C!

weatherStation.unsubscribe(phoneDisplay);
weatherStation.setState({ temperature: 25, humidity: 70, weather: '多云' });
// 手机屏不再收到通知

🛠️ 实际应用

Vue 的响应式系统、RxJS、Node.js 的 EventEmitter 都是观察者模式的实现。


14. 发布-订阅模式(Pub/Sub)

🎯 一句话理解

和观察者模式的区别:发布者和订阅者 互不认识 ,通过一个 消息中心 间接通信。像微信公众号一样。

🏠 生活类比

观察者模式:老师直接通知学生(知道学生是谁)。发布-订阅模式:你关注了公众号"前端早读课",编辑发文章你就能收到。编辑不知道你是谁,你也不知道编辑是谁,中间人是微信平台。

💻 代码实现

javascript 复制代码
// 消息中心(Event Bus)
class EventBus {
  static #instance = null;
  #events = {};

  constructor() {
    if (EventBus.#instance) return EventBus.#instance;
    EventBus.#instance = this;
  }

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

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

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

  // 发布
  emit(event, ...args) {
    if (!this.#events[event]) return;
    this.#events[event].forEach(cb => cb(...args));
  }

  clear() { this.#events = {}; }
}

// 使用 ------ 跨组件通信
const bus = new EventBus();

// 组件A:监听用户登录
const unsub = bus.on('user:login', (user) => {
  console.log(`📊 数据面板: 欢迎 ${user.name}`);
});
bus.on('user:login', (user) => {
  console.log(`🔔 通知栏: ${user.name} 上线了`);
});

// 组件B:触发登录
bus.emit('user:login', { name: '张三', role: 'admin' });
// 📊 数据面板: 欢迎 张三
// 🔔 通知栏: 张三 上线了

// 取消订阅
unsub();

📊 观察者 vs 发布-订阅

对比 观察者模式 发布-订阅模式
通信方式 直接通信 通过消息中心间接通信
耦合度 较高 很低(互不知道)
可扩展性 一般 很强
代表实现 Vue 响应式、RxJS EventBus、Redux、MQ

15. 策略模式(Strategy)

🎯 一句话理解

把一系列 算法 封装起来,让它们可以互相替换。消除 if-else 地狱

🏠 生活类比

你从北京去上海,可以坐飞机、高铁、自驾、大巴。每种交通方式就是一个"策略"。你不用关心每种方式的具体细节,选择一种就行。

💻 代码实现

javascript 复制代码
// 策略集合
const pricingStrategies = {
  normal: {
    calculate(price) { return price; },
    getLabel() { return '普通价'; }
  },
  silver: {
    calculate(price) { return price * 0.9; },
    getLabel() { return '银牌9折'; }
  },
  gold: {
    calculate(price) {
      let finalPrice = price * 0.8;
      if (finalPrice >= 200) finalPrice -= 20;
      return finalPrice;
    },
    getLabel() { return '金牌8折+满减'; }
  },
  diamond: {
    calculate(price) { return Math.max(0, price * 0.7 - 30); },
    getLabel() { return '钻石7折+立减'; }
  }
};

// 价格计算器(Context)
class PriceCalculator {
  constructor(strategy = 'normal') { this.setStrategy(strategy); }

  setStrategy(strategy) {
    if (!pricingStrategies[strategy]) throw new Error(`未知策略: ${strategy}`);
    this.strategy = pricingStrategies[strategy];
    console.log(`🔄 切换到: ${this.strategy.getLabel()}`);
    return this;
  }

  calculate(price) {
    const finalPrice = this.strategy.calculate(price);
    console.log(`💰 原价: ¥${price} → ${this.strategy.getLabel()}: ¥${finalPrice.toFixed(2)}`);
    return finalPrice;
  }
}

const calc = new PriceCalculator('normal');
calc.calculate(300);                        // ¥300.00
calc.setStrategy('silver').calculate(300);  // ¥270.00
calc.setStrategy('gold').calculate(300);    // ¥220.00
calc.setStrategy('diamond').calculate(300); // ¥180.00

表单验证策略:

javascript 复制代码
const validators = {
  required: (value, msg = '必填') => value.trim() ? null : msg,
  minLength: (value, min, msg) => value.length >= min ? null : (msg || `最少${min}个字符`),
  email: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) ? null : '邮箱格式不正确',
  phone: (value) => /^1[3-9]\d{9}$/.test(value) ? null : '手机号格式不正确',
};

class FormValidator {
  #rules = [];
  addRule(field, value, ...strategies) {
    this.#rules.push({ field, value, strategies });
    return this;
  }
  validate() {
    const errors = {};
    for (const rule of this.#rules) {
      for (const [name, ...args] of rule.strategies) {
        const error = validators[name](rule.value, ...args);
        if (error) {
          if (!errors[rule.field]) errors[rule.field] = [];
          errors[rule.field].push(error);
          break;
        }
      }
    }
    return Object.keys(errors).length ? errors : null;
  }
}

const v = new FormValidator();
v.addRule('username', 'ab', ['required'], ['minLength', 3, '用户名至少3个字符'])
 .addRule('email', 'bad', ['required'], ['email']);

console.log(v.validate());
// { username: ['用户名至少3个字符'], email: ['邮箱格式不正确'] }

16. 迭代器模式(Iterator)

🎯 一句话理解

提供一种方法 顺序访问 集合中的元素,而不暴露集合的内部结构。

🏠 生活类比

自助餐厅的传送带。你不需要知道厨房是怎么摆盘的,只需要等传送带把菜一道一道送到你面前。

💻 代码实现

javascript 复制代码
// 自定义可迭代对象
class NumberRange {
  constructor(start, end, step = 1) {
    this.start = start; this.end = end; this.step = step;
  }

  [Symbol.iterator]() {
    let current = this.start;
    const self = this;
    return {
      next() {
        if (current <= self.end) {
          const value = current;
          current += self.step;
          return { value, done: false };
        }
        return { value: undefined, done: true };
      }
    };
  }
}

// 可以用 for...of 遍历!
for (const num of new NumberRange(1, 10, 2)) {
  console.log(num); // 1, 3, 5, 7, 9
}
console.log([...new NumberRange(0, 5)]); // [0, 1, 2, 3, 4, 5]

二叉树遍历迭代器:

javascript 复制代码
class TreeNode {
  constructor(value, left = null, right = null) {
    this.value = value; this.left = left; this.right = right;
  }
}

class BinaryTree {
  constructor(root) { this.root = root; }

  // 中序遍历(左 → 根 → 右)------ 升序输出
  *[Symbol.iterator]() {
    function* inorder(node) {
      if (!node) return;
      yield* inorder(node.left);
      yield node.value;
      yield* inorder(node.right);
    }
    yield* inorder(this.root);
  }

  // 前序遍历
  *preorder() {
    function* pre(node) {
      if (!node) return;
      yield node.value;
      yield* pre(node.left);
      yield* pre(node.right);
    }
    yield* pre(this.root);
  }
}

const tree = new BinaryTree(
  new TreeNode(4,
    new TreeNode(2, new TreeNode(1), new TreeNode(3)),
    new TreeNode(6, new TreeNode(5), new TreeNode(7))
  )
);

console.log([...tree]);           // [1, 2, 3, 4, 5, 6, 7]
console.log([...tree.preorder()]); // [4, 2, 1, 3, 6, 5, 7]

17. 模板方法模式(Template Method)

🎯 一句话理解

在父类中定义 算法的骨架,把某些步骤延迟到子类实现。子类可以重写细节,但不能改变整体流程。

🏠 生活类比

做菜有一个固定流程:备料 → 下锅 → 调味 → 装盘。这个流程是固定的(模板),但每道菜的具体做法不同。炒青菜和红烧肉都走同样的步骤,但具体内容大不相同。

💻 代码实现

javascript 复制代码
// 模板类:数据解析器
class DataParser {
  // 模板方法 ------ 定义算法骨架
  parse(input) {
    console.log('--- 开始解析 ---');
    const raw = this.read(input);
    const validated = this.validate(raw);
    const processed = this.process(validated);
    const result = this.format(processed);
    console.log('--- 解析完成 ---');
    return result;
  }

  // 具体步骤 ------ 子类实现
  read(input) { throw new Error('必须实现 read'); }
  validate(data) {
    if (!data) throw new Error('数据为空');
    console.log('✅ 验证通过');
    return data;
  }
  process(data) { throw new Error('必须实现 process'); }
  format(data) {
    console.log('📋 格式化输出');
    return JSON.stringify(data, null, 2);
  }
}

// CSV 解析器
class CsvParser extends DataParser {
  read(input) {
    console.log('📄 读取 CSV');
    return input.split('\n').map(line => line.split(','));
  }
  process(data) {
    const headers = data[0];
    return data.slice(1).map(row => {
      const obj = {};
      headers.forEach((h, i) => obj[h.trim()] = row[i]?.trim());
      return obj;
    });
  }
}

// JSON 解析器
class JsonParser extends DataParser {
  read(input) { return JSON.parse(input); }
  validate(data) {
    super.validate(data);
    if (!Array.isArray(data)) throw new Error('必须是数组');
    return data;
  }
  process(data) { return data.filter(item => item.active !== false); }
}

// 使用 ------ 流程固定,实现不同
const csv = new CsvParser();
csv.parse('name,age\n张三,25\n李四,30');

18. 命令模式(Command)

🎯 一句话理解

请求 封装成对象,使你可以参数化、排队、记录日志、支持 撤销操作

🏠 生活类比

餐厅点餐:你写一张菜单(命令),交给服务员(调用者),服务员传给后厨(接收者)。你和后厨没直接接触,中间通过"命令"传达。而且你可以取消订单(撤销)。

💻 代码实现

javascript 复制代码
// 接收者
class Light { on() { console.log('💡 灯开了'); } off() { console.log('💡 灯关了'); } }
class TV { on() { console.log('📺 电视开了'); } off() { console.log('📺 电视关了'); } }

// 命令接口
class Command {
  execute() { throw new Error('必须实现'); }
  undo() { throw new Error('必须实现'); }
}

class LightOnCommand extends Command {
  constructor(light) { super(); this.light = light; }
  execute() { this.light.on(); }
  undo() { this.light.off(); }
}

class TVOnCommand extends Command {
  constructor(tv) { super(); this.tv = tv; }
  execute() { this.tv.on(); }
  undo() { this.tv.off(); }
}

// 宏命令:一键执行多个命令
class MacroCommand extends Command {
  #commands = [];
  add(cmd) { this.#commands.push(cmd); return this; }
  execute() { this.#commands.forEach(cmd => cmd.execute()); }
  undo() { [...this.#commands].reverse().forEach(cmd => cmd.undo()); }
}

// 遥控器(调用者)------ 支持撤销
class RemoteControl {
  #history = [];

  execute(command) {
    command.execute();
    this.#history.push(command);
  }

  undo() {
    const command = this.#history.pop();
    if (command) command.undo();
    else console.log('⚠ 没有可撤销的操作');
  }
}

// 使用
const remote = new RemoteControl();
const light = new Light();
const tv = new TV();

// 一键"回家模式"
const homeMode = new MacroCommand();
homeMode.add(new LightOnCommand(light)).add(new TVOnCommand(tv));

remote.execute(homeMode);
// 💡 灯开了 → 📺 电视开了

remote.undo();
// 📺 电视关了 → 💡 灯关了(逆序撤销)

19. 职责链模式(Chain of Responsibility)

🎯 一句话理解

把请求沿着 链条 传递,每个节点决定自己处理还是传给下一个。像审批流程一样。

🏠 生活类比

公司报销审批:金额 < 500 → 组长批,500 ~ 5000 → 经理批,> 5000 → 总监批。报销单从组长开始往上递,每个人决定自己能不能批,批不了就传给上级。

💻 代码实现

javascript 复制代码
// 处理器基类
class Handler {
  #next = null;
  setNext(handler) { this.#next = handler; return handler; }
  handle(request) {
    if (this.#next) return this.#next.handle(request);
    return null;
  }
}

// 具体处理器
class GroupLeader extends Handler {
  handle(request) {
    if (request.amount <= 500) {
      return `✅ 组长批准: ¥${request.amount} (${request.reason})`;
    }
    console.log(`组长: ¥${request.amount} 超出权限,转交经理`);
    return super.handle(request);
  }
}

class Manager extends Handler {
  handle(request) {
    if (request.amount <= 5000) {
      return `✅ 经理批准: ¥${request.amount} (${request.reason})`;
    }
    console.log(`经理: ¥${request.amount} 超出权限,转交总监`);
    return super.handle(request);
  }
}

class Director extends Handler {
  handle(request) {
    if (request.amount <= 50000) {
      return `✅ 总监批准: ¥${request.amount} (${request.reason})`;
    }
    return `❌ 总监拒绝: ¥${request.amount} 金额过大,需要CEO审批`;
  }
}

// 构建责任链
const leader = new GroupLeader();
const manager = new Manager();
const director = new Director();
leader.setNext(manager).setNext(director);

// 使用
console.log(leader.handle({ amount: 200, reason: '办公用品' }));
// ✅ 组长批准: ¥200 (办公用品)

console.log(leader.handle({ amount: 3000, reason: '团建费用' }));
// 组长: ¥3000 超出权限,转交经理
// ✅ 经理批准: ¥3000 (团建费用)

console.log(leader.handle({ amount: 20000, reason: '服务器采购' }));
// 组长 → 经理 → ✅ 总监批准: ¥20000 (服务器采购)

中间件链(Koa 风格):

javascript 复制代码
class MiddlewareChain {
  #middlewares = [];

  use(middleware) {
    this.#middlewares.push(middleware);
    return this;
  }

  async execute(ctx) {
    let index = 0;

    const dispatch = async (i) => {
      if (i >= this.#middlewares.length) return;
      const middleware = this.#middlewares[i];
      await middleware(ctx, () => dispatch(i + 1));
    };

    await dispatch(index);
    return ctx;
  }
}

// 使用 ------洋葱模型
const chain = new MiddlewareChain();

chain
  .use(async (ctx, next) => {
    console.log('🔑 认证中间件 - 开始');
    ctx.user = { name: '张三' };
    await next();
    console.log('🔑 认证中间件 - 结束');
  })
  .use(async (ctx, next) => {
    console.log(`📝 日志: ${ctx.user.name} 访问了 ${ctx.path}`);
    await next();
  })
  .use(async (ctx, next) => {
    console.log('⚡ 业务处理');
    ctx.body = { message: '成功' };
    await next();
  });

chain.execute({ path: '/api/data' });
// 🔑 认证中间件 - 开始
// 📝 日志: 张三 访问了 /api/data
// ⚡ 业务处理
// 🔑 认证中间件 - 结束

20. 状态模式(State)

🎯 一句话理解

对象的 行为 取决于它的 状态。把每个状态的行为封装成独立的类,消除大量的状态判断 if-else。

🏠 生活类比

红绿灯:红灯 → 停车,绿灯 → 行驶,黄灯 → 减速。同样的"看到信号灯"动作,在不同状态下行为完全不同。

💻 代码实现

javascript 复制代码
// 状态接口
class OrderState {
  pay(order) { throw new Error('当前状态不支持支付'); }
  ship(order) { throw new Error('当前状态不支持发货'); }
  deliver(order) { throw new Error('当前状态不支持确认收货'); }
  cancel(order) { throw new Error('当前状态不支持取消'); }
  getName() { throw new Error('必须实现'); }
}

// 待支付状态
class PendingState extends OrderState {
  pay(order) {
    console.log('💰 支付成功!');
    order.setState(new PaidState());
  }
  cancel(order) {
    console.log('❌ 订单已取消');
    order.setState(new CancelledState());
  }
  getName() { return '待支付'; }
}

// 已支付状态
class PaidState extends OrderState {
  ship(order) {
    console.log('📦 已发货!');
    order.setState(new ShippedState());
  }
  cancel(order) {
    console.log('💰 申请退款中...');
    order.setState(new CancelledState());
  }
  getName() { return '已支付'; }
}

// 已发货状态
class ShippedState extends OrderState {
  deliver(order) {
    console.log('✅ 已确认收货!');
    order.setState(new DeliveredState());
  }
  getName() { return '已发货'; }
}

// 已完成状态
class DeliveredState extends OrderState {
  getName() { return '已完成'; }
}

// 已取消状态
class CancelledState extends OrderState {
  getName() { return '已取消'; }
}

// 订单类
class Order {
  #state;

  constructor(orderId) {
    this.orderId = orderId;
    this.#state = new PendingState();
    console.log(`📋 订单 ${orderId} 已创建,状态: ${this.getStatus()}`);
  }

  setState(state) {
    console.log(`📊 状态变更: ${this.#state.getName()} → ${state.getName()}`);
    this.#state = state;
  }

  getStatus() { return this.#state.getName(); }

  pay() { this.#state.pay(this); }
  ship() { this.#state.ship(this); }
  deliver() { this.#state.deliver(this); }
  cancel() { this.#state.cancel(this); }
}

// 使用 ------ 状态自动管理,不会出现非法操作
const order = new Order('ORD-001');
// 📋 订单 ORD-001 已创建,状态: 待支付

order.pay();
// 💰 支付成功!
// 📊 状态变更: 待支付 → 已支付

order.ship();
// 📦 已发货!
// 📊 状态变更: 已支付 → 已发货

order.deliver();
// ✅ 已确认收货!
// 📊 状态变更: 已发货 → 已完成

// order.ship(); // ❌ Error: 当前状态不支持发货(已完成不能再发货)

四、设计模式之间的关系

🕸️ 模式对比与关联

复制代码
创建型                     结构型                     行为型
┌──────────┐         ┌──────────┐         ┌──────────┐
│ 单例     │         │ 适配器   │         │ 观察者   │
│ 工厂     │         │ 装饰器 ←─┼─相似───→│ 发布订阅 │
│ 抽象工厂 │         │ 代理  ←──┼─相似    │ 策略     │
│ 建造者   │         │ 外观     │         │ 迭代器   │
│ 原型     │         │ 桥接     │         │ 模板方法 │
└──────────┘         │ 组合     │         │ 命令     │
                     │ 享元     │         │ 职责链   │
                     └──────────┘         │ 状态     │
                                          └──────────┘

🔗 容易混淆的模式对比

对比组 核心区别 判断标准
工厂 vs 建造者 工厂关注"创建什么",建造者关注"怎么一步步创建" 需要分步骤构建?→ 建造者
工厂 vs 抽象工厂 工厂创建一种产品,抽象工厂创建一系列产品 需要产品族?→ 抽象工厂
代理 vs 装饰器 代理控制访问,装饰器增强功能 是否限制访问?→ 代理
代理 vs 适配器 代理接口不变,适配器改变接口 接口不同?→ 适配器
外观 vs 代理 外观简化接口,代理控制访问 简化复杂性?→ 外观
观察者 vs 发布订阅 观察者直接通信,发布订阅通过中间人 有消息中心?→ 发布订阅
策略 vs 状态 策略由客户端选择,状态由对象内部切换 状态自动切换?→ 状态模式
模板方法 vs 策略 模板方法靠继承,策略靠组合 用继承还是组合?
命令 vs 策略 命令关注"做什么"(可撤销),策略关注"怎么做" 需要撤销?→ 命令

五、总结速查表

# 模式 类型 一句话 常见场景
1 单例 创建型 全局唯一实例 Vuex Store、全局弹窗
2 工厂 创建型 你要什么我帮你造 React.createElement、组件工厂
3 抽象工厂 创建型 一系列产品的工厂 跨平台UI组件库
4 建造者 创建型 一步步搭积木 SQL Builder、axios配置
5 原型 创建型 克隆一个已有的 Object.create、深拷贝
6 适配器 结构型 万能转换插头 第三方SDK对接、数据格式转换
7 装饰器 结构型 不改原物,层层增强 HOC、函数AOP、TS装饰器
8 代理 结构型 找个中间人控制访问 Vue3 reactive、缓存代理
9 外观 结构型 复杂系统一键操作 API模块封装、智能家居
10 桥接 结构型 抽象与实现分离 消息系统(渠道×类型)
11 组合 结构型 树形结构统一操作 DOM树、组件树、文件系统
12 享元 结构型 共享减少内存 虚拟列表、字体对象池
13 观察者 行为型 变了就通知你 Vue响应式、EventEmitter
14 发布订阅 行为型 通过中间人间接通信 EventBus、Redux、消息队列
15 策略 行为型 消灭if-else 价格计算、表单验证
16 迭代器 行为型 一个一个往外拿 for...of、自定义遍历
17 模板方法 行为型 骨架固定,细节自定义 数据解析流程、算法框架
18 命令 行为型 封装请求,支持撤销 宏命令、编辑器撤销重做
19 职责链 行为型 沿着链条传递 审批流程、中间件
20 状态 行为型 状态决定行为 订单状态机、红绿灯

六、学习路线图

复制代码
入门(先掌握这5个)          进阶(再学这7个)           高级(最后8个)
┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│ ✅ 单例模式      │     │ 🔶 抽象工厂      │     │ 🔷 桥接模式      │
│ ✅ 工厂模式      │     │ 🔶 代理模式      │     │ 🔷 组合模式      │
│ ✅ 观察者模式    │     │ 🔶 外观模式      │     │ 🔷 享元模式      │
│ ✅ 策略模式      │     │ 🔶 装饰器模式    │     │ 🔷 迭代器模式    │
│ ✅ 发布-订阅     │     │ 🔶 适配器模式    │     │ 🔷 模板方法      │
│                  │     │ 🔶 命令模式      │     │ 🔷 职责链模式    │
│                  │     │ 🔶 状态模式      │     │ 🔷 建造者模式    │
│                  │     │                  │     │ 🔷 原型模式      │
└─────────────────┘     └─────────────────┘     └─────────────────┘

📝 学习建议

  1. 不要死记硬背:理解每种模式解决什么问题,比记住代码更重要
  2. 多看源码:Vue、React、Koa、Express 中到处都是设计模式
  3. 刻意练习:在项目中遇到 if-else 地狱时,想想能不能用策略/状态模式
  4. 对比学习:把相似的模式放在一起比较(如代理 vs 装饰器 vs 适配器)
  5. 循序渐进:先掌握入门的5个,用到熟练了再学进阶的

🔍 源码中的设计模式

框架/库 用到的模式
Vue 3 代理模式(reactive)、观察者模式(依赖收集)、发布订阅(事件总线)、策略模式(diff算法)
React 工厂模式(createElement)、装饰器模式(HOC)、组合模式(组件树)、观察者模式(Hooks)
Koa 职责链模式(中间件)、外观模式(ctx封装)
Express 职责链模式(中间件)、工厂模式(Router)、模板方法(生命周期)
Axios 适配器模式(xhr/fetch适配)、建造者模式(拦截器配置)、外观模式(API封装)
Redux 发布订阅模式(subscribe)、单例模式(store)、命令模式(action)
RxJS 观察者模式(核心)、迭代器模式(Observable)、职责链(Operator)

💬 写在最后:设计模式不是银弹,不要为了用而用。当你发现代码越来越难维护、if-else 越来越多、改一个地方牵一发动全身的时候,再回头看看这篇文章,也许某个模式正好能帮到你。

好的代码不是一开始就设计出来的,而是在不断重构中打磨出来的。

如果觉得有帮助,请点个 👍 收藏,以后用得上的时候能找到。有问题欢迎评论区讨论!

相关推荐
信竞星球_少儿编程题库6 小时前
2026年全国信息素养大赛算法应用主题赛 丝路新城 Python 模拟卷(三)
开发语言·python·算法
江湖中的阿龙7 小时前
【无标题】
java·开发语言
wjs20247 小时前
SVG 渐变-放射性
开发语言
聚美智数7 小时前
食物热量搜索与详情双接口接入实战,轻量化生活服务 API 落地
java·开发语言·生活
basketball6167 小时前
并查集基础算法总结 C++ 实现
开发语言·c++·算法
小陶来咯7 小时前
AI Agent 设计模式:ReAct 深度解析
人工智能·react.js·设计模式
凤凰院凶涛QAQ7 小时前
《C++转Java快速入手系列》String篇:在C++里拼字符串像搬砖,在Java里拼字符串像玩乐高 —— 还是带垃圾回收的那种。
java·开发语言·c++