JavaScript 设计模式(23 种)


文章目录

    • 设计模式简介
    • 设计模式原则(SOLID)
    • 一、创建型模式
      • [1. 工厂模式](#1. 工厂模式)
      • [2. 单例模式](#2. 单例模式)
      • [3. 原型模式](#3. 原型模式)
      • [4. 建造者模式](#4. 建造者模式)
      • [5. 抽象工厂模式](#5. 抽象工厂模式)
    • 二、结构型模式
      • [6. 适配器模式](#6. 适配器模式)
      • [7. 装饰器模式](#7. 装饰器模式)
      • [8. 代理模式](#8. 代理模式)
      • [9. 外观模式](#9. 外观模式)
      • [10. 桥接模式](#10. 桥接模式)
      • [11. 组合模式](#11. 组合模式)
      • [12. 享元模式](#12. 享元模式)
    • 三、行为型模式
      • [13. 观察者模式(发布---订阅)](#13. 观察者模式(发布—订阅))
      • [14. 策略模式](#14. 策略模式)
      • [15. 模板方法模式](#15. 模板方法模式)
      • [16. 职责链模式](#16. 职责链模式)
      • [17. 命令模式](#17. 命令模式)
      • [18. 状态模式](#18. 状态模式)
      • [19. 迭代器模式](#19. 迭代器模式)
      • [20. 备忘录模式](#20. 备忘录模式)
      • [21. 中介者模式](#21. 中介者模式)
      • [22. 访问者模式](#22. 访问者模式)
      • [23. 解释器模式](#23. 解释器模式)
    • 模式对比速查表

设计模式简介

设计模式是软件开发中反复出现的问题的通用解决方案,是经过大量实践验证的最佳实践总结。使用设计模式的核心价值:

  • 重用代码 --- 避免重复造轮子
  • 易于理解 --- 用公认的术语描述架构意图
  • 保证可靠性 --- 经过验证的成熟方案

设计模式原则(SOLID)

原则 说明 白话理解
S 单一职责 一个程序只做好一件事 功能复杂了就拆分
O 开放封闭 对扩展开放,对修改封闭 加功能加新代码,别改老代码
L 里氏替换 子类能覆盖父类 父类能出现的地方子类就能出现
I 接口隔离 保持接口的单一独立 别让一个接口干所有事
D 依赖倒转 面向接口编程 依赖抽象,不依赖具体实现

举个栗子 --- Promise 就体现了 S 和 O:

  • 单一职责 :每个 .then() 只做一件事
  • 开放封闭 :新增需求只需追加 .then(),无需改动已有链

再来一个反例 → 正例的演化:

js 复制代码
// ❌ 反例:每次加校验规则都要改函数内部 --- 违反开放封闭原则
const checkType = (str, type) => {
  switch (type) {
    case 'email':    return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str)
    case 'mobile':   return /^1[3|4|5|7|8][0-9]{9}$/.test(str)
    case 'tel':      return /^(0\d{2,3}-\d{7,8})(-\d{1,4})?$/.test(str)
    default:         return true
  }
}

// ✅ 正例:开放扩展,封闭修改
const createValidator = () => {
  const rules = {
    email:  str => /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str),
    mobile: str => /^1[3|4|5|7|8][0-9]{9}$/.test(str),
  }

  return {
    check: (str, type) => (rules[type] ? rules[type](str) : false),
    addRule: (type, fn) => { rules[type] = fn },
  }
}

const validator = createValidator()
validator.addRule('money', str => /^[0-9]+(\.[0-9]{2})?$/.test(str))

console.log(validator.check('18817023900', 'mobile')) // true
console.log(validator.check('18.36', 'money'))         // true

一、创建型模式

1. 工厂模式

定义一个创建对象的接口,由子类决定实例化哪个类,将 new 操作封装起来。

核心思想:把"创建什么对象"的决策从调用方移走,调用方只关心拿到的对象能干什么。

js 复制代码
// 产品:用户
const createUser = (name, role) => {
  const say = () => console.log(`我是${role}:${name}`)

  return role === 'admin'
    ? { name, role, say, manage: () => console.log('管理系统权限') }
    : { name, role, say }
}

// 工厂
const userFactory = {
  create: (name, role) => createUser(name, role),
}

// 使用
const admin = userFactory.create('小明', 'admin')
const guest = userFactory.create('小红', 'guest')

admin.say()   // 我是admin:小明
guest.say()   // 我是guest:小红

现实中的例子:

js 复制代码
// jQuery 的 $() 就是一个工厂 --- 根据传入参数不同创建不同的 jQuery 对象
const $ = (selector) => {
  const el = typeof selector === 'string'
    ? document.querySelectorAll(selector)
    : selector
  return {
    el,
    addClass: (cls) => { /* ... */ },
    on: (event, fn) => { /* ... */ },
  }
}
优点 缺点
创建过程复杂时,调用方只需关心结果 简单场景用工厂反而多此一举
构造函数和创建者分离,符合开闭原则 增加产品时要加新的工厂函数
扩展性好 引入抽象层,增加理解成本

适用场景:需要根据环境创建不同实例,且这些实例有相同行为时。

什么时候不用 :如果只是简单的 new,直接用构造函数更清晰。


2. 单例模式

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

js 复制代码
// 弹窗管理器 --- 整个应用只需要一个
const createModal = () => {
  let modalEl = null

  const show = (content) => {
    if (modalEl) {
      modalEl.style.display = 'block'
      return
    }
    modalEl = document.createElement('div')
    modalEl.className = 'modal'
    modalEl.textContent = content
    document.body.appendChild(modalEl)
  }

  const hide = () => {
    if (modalEl) modalEl.style.display = 'none'
  }

  return { show, hide }
}

// 利用闭包 + IIFE 保证单例
const getModal = (() => {
  let instance = null
  return () => {
    if (!instance) instance = createModal()
    return instance
  }
})()

// 使用
const m1 = getModal()
const m2 = getModal()
console.log(m1 === m2) // true --- 全局唯一

现实中的例子:

js 复制代码
// Vuex Store --- 一个应用只有一个 store
// Redux createStore --- 单例 store
// 全局缓存、全局配置对象
const globalConfig = (() => {
  const config = {}
  return {
    get: (key) => config[key],
    set: (key, val) => { config[key] = val },
  }
})()
优点 缺点
减少全局变量,增强模块性 强耦合,不利于单元测试
只实例化一次,节省内存 隐含的全局状态,调试困难

适用场景:登录弹窗、全局 store、数据库连接池、配置文件管理器。


3. 原型模式

用原型实例指定创建对象的种类,通过拷贝原型来创建新对象。

js 复制代码
// 汽车原型
const carPrototype = {
  wheels: 4,
  drive() { console.log(`${this.brand} 正在行驶...`) },
}

// 基于原型创建新对象
const createCar = (brand) => {
  const car = Object.create(carPrototype)
  car.brand = brand
  return car
}

const tesla = createCar('Tesla')
const bmw = createCar('BMW')

tesla.drive() // Tesla 正在行驶...
bmw.drive()   // BMW 正在行驶...
console.log(tesla.wheels) // 4(来自原型)

现实中的例子:

js 复制代码
// Object.create() 就是原型模式的直接应用
// JS 中 extends 本质上也是原型继承

// 配置模板 --- 创建多个带默认值的配置对象
const defaultConfig = { timeout: 3000, retry: 3, cache: true }
const apiConfig = { ...defaultConfig, url: '/api/users' }  // 展开运算符也是拷贝原型的思想
优点 缺点
通过拷贝创建重复对象,提升性能 深拷贝嵌套对象可能复杂
简化新对象创建

适用场景:需要大量创建相似对象,且创建成本较高时。


4. 建造者模式

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

核心思想:分步骤构建一个复杂对象,每一步只关注自己负责的部分,最终拼装出完整对象。

js 复制代码
// 建造一个汉堡套餐
const createMealBuilder = () => {
  const meal = {}

  const builder = {
    setMain: (main) => { meal.main = main; return builder },
    setDrink: (drink) => { meal.drink = drink; return builder },
    setSnack: (snack) => { meal.snack = snack; return builder },
    build: () => ({ ...meal, describe: () =>
      console.log(`套餐:${meal.main} + ${meal.drink} + ${meal.snack}`)
    }),
  }
  return builder
}

// 使用
const mealA = createMealBuilder()
  .setMain('汉堡')
  .setDrink('可乐')
  .setSnack('薯条')
  .build()

mealA.describe() // 套餐:汉堡 + 可乐 + 薯条

现实中的例子:

js 复制代码
// 表单构建器
const createFormBuilder = () => {
  const fields = []
  const builder = {
    addInput: (label, name) => {
      fields.push({ type: 'input', label, name })
      return builder
    },
    addSelect: (label, name, options) => {
      fields.push({ type: 'select', label, name, options })
      return builder
    },
    build: () => ({ fields, render: () => console.log('渲染表单:', fields) }),
  }
  return builder
}
优点 缺点
构建过程清晰可控,链式调用语义好 代码量增加
构建与表示分离,可创建不同表示 不适合简单对象(杀鸡用牛刀)

适用场景:需要构造包含多个可选参数的对象、构建步骤固定的复杂对象。


5. 抽象工厂模式

提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

和普通工厂的区别 :普通工厂生产一种产品,抽象工厂生产一组相关产品(产品族)。

js 复制代码
// 抽象工厂:UI 组件工厂 --- 为不同平台(Web / Mobile)创建一整套 UI 组件
const createWebUI = () => ({
  button: (text) => ({ render: () => `<button>${text}</button>` }),
  input: (placeholder) => ({ render: () => `<input placeholder="${placeholder}">` }),
})

const createMobileUI = () => ({
  button: (text) => ({ render: () => `[MobileBtn ${text}]` }),
  input: (placeholder) => ({ render: () => `[MobileInput hint="${placeholder}"]` }),
})

// 使用
const uiFactory = (platform) => platform === 'web' ? createWebUI() : createMobileUI()

const ui = uiFactory('web')
console.log(ui.button('提交').render()) // <button>提交</button>

现实中的例子:

js 复制代码
// 不同数据库驱动返回一整套操作(connect, query, close)
const createMySQLDriver = () => ({
  connect: () => console.log('MySQL 连接'),
  query: (sql) => console.log(`MySQL 执行:${sql}`),
})

const createPgDriver = () => ({
  connect: () => console.log('PostgreSQL 连接'),
  query: (sql) => console.log(`Pg 执行:${sql}`),
})
优点 缺点
保证产品族内对象的一致性 增加新产品族结构时需要改动工厂接口
客户端与具体产品解耦 结构较复杂

二、结构型模式

6. 适配器模式

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

核心思想:像电源转换插头一样,在中间做一层"翻译"。

js 复制代码
// 新接口
const newRequest = ({ url, method = 'GET', data = {} }) =>
  fetch(url, { method, body: JSON.stringify(data), headers: { 'Content-Type': 'application/json' } })
    .then(res => res.json())

// 旧代码全用 $.ajax 的格式,做个适配器包一层
const $ = {
  ajax: ({ url, type = 'GET', dataType = 'json', data = {} }) =>
    newRequest({ url, method: type, data })
      .then(res => ({
        data: res,
        status: 200,
      })),
}

// 老代码不用改,直接用
$.ajax({ url: '/api/user', type: 'GET' }).then(({ data }) => console.log(data))

现实中的例子:

js 复制代码
// Vue computed --- 把 data 适配成模板需要的格式
const vm = {
  data: { firstName: 'Zhang', lastName: 'San' },
  computed: {
    fullName: ({ firstName, lastName }) => `${firstName} ${lastName}`, // 适配!
  },
}

// 把第三方 SDK 的数据格式转成自己项目的格式
const adaptUserData = (thirdPartyUser) => ({
  id: thirdPartyUser.user_id,
  name: thirdPartyUser.nickname,
  avatar: thirdPartyUser.head_img_url,
})
优点 缺点
让不兼容的接口可以协作 增加了一层调用
提高了类的复用度 如果可以直接改原接口,就别用适配器

🆚 适配器 vs 代理 :适配器提供不同的接口 ,代理提供一模一样的接口。


7. 装饰器模式

不改变原对象,通过包装来动态添加功能。

核心思想:像给手机套壳 --- 手机本身没变,但多了保护功能。

js 复制代码
// 原函数:提交订单
const submitOrder = (order) => {
  console.log(`订单 ${order.id} 提交成功`)
}

// 装饰器:加上日志记录
const withLogging = (fn) => (...args) => {
  console.log(`[${new Date().toISOString()}] 开始执行`)
  const result = fn(...args)
  console.log(`[${new Date().toISOString()}] 执行完成`)
  return result
}

// 装饰器:加上性能监控
const withTimer = (fn) => (...args) => {
  const start = Date.now()
  const result = fn(...args)
  console.log(`耗时:${Date.now() - start}ms`)
  return result
}

// 叠加装饰
const enhancedSubmit = withTimer(withLogging(submitOrder))
enhancedSubmit({ id: 'A001' })

现实中的例子:

js 复制代码
// React HOC(高阶组件)
const withAuth = (Component) => (props) => {
  if (!props.isLogin) return <LoginPage />
  return <Component {...props} />
}

// Express/Koa 中间件 --- 在请求前后添加行为
const logger = (handler) => async (req, res) => {
  console.log(`${req.method} ${req.url}`)
  return handler(req, res)
}
优点 缺点
动态扩展功能,比继承更灵活 多层装饰可能让调用链复杂
装饰者和被装饰者解耦 产生很多小对象

8. 代理模式

为对象提供一个替身,以控制对这个对象的访问。

核心思想:你想追一个女孩,不直接送花,而是通过她的闺蜜在最合适的时机转交。

js 复制代码
// 原始图片加载
const loadImage = (src) => {
  const img = new Image()
  img.src = src
  return img
}

// 代理:加占位图和懒加载
const createImageProxy = (src, placeholder = '/loading.gif') => {
  const img = new Image()
  img.src = placeholder  // 先显示占位图

  const realImg = new Image()
  realImg.onload = () => { img.src = src }  // 真实图片加载完再替换
  realImg.src = src

  return { img, getSrc: () => img.src }
}

现实中的例子:

js 复制代码
// ES6 Proxy
const api = { getUser: (id) => fetch(`/api/user/${id}`) }
const apiWithCache = new Proxy(api, {
  cache: {},
  get(target, key) {
    const fn = target[key]
    return async (...args) => {
      const cacheKey = `${key}_${JSON.stringify(args)}`
      if (this.cache[cacheKey]) return this.cache[cacheKey]
      const result = await fn(...args)
      this.cache[cacheKey] = result
      return result
    }
  },
})

// DOM 事件代理
document.getElementById('list').addEventListener('click', (e) => {
  if (e.target.tagName === 'LI') console.log('点击了:', e.target.textContent)
})
优点 缺点
保护目标对象,控制访问 多一层间接调用,可能有性能差异
可扩展功能,符合开闭原则

🆚 代理 vs 装饰器 :代理是控制访问 (加限制),装饰器是扩展功能(不改变原有使用方式)。


9. 外观模式

为子系统的一组接口提供统一的高层接口,让子系统更容易使用。

核心思想:给你的复杂系统提供"一键操作"按钮。

js 复制代码
// 电脑开机 --- 内部很复杂,对外只需要一个按钮
const cpu = { start: () => console.log('CPU 启动') }
const memory = { load: () => console.log('内存加载') }
const disk = { read: () => console.log('硬盘读取') }

// 外观 --- 一键开机
const powerButton = {
  press: () => {
    cpu.start()
    memory.load()
    disk.read()
    console.log('✅ 开机完成')
  },
}

powerButton.press()

现实中的例子:

js 复制代码
// 兼容不同浏览器的事件绑定
const on = (el, event, handler) => {
  if (el.addEventListener) return el.addEventListener(event, handler, false)
  if (el.attachEvent) return el.attachEvent(`on${event}`, handler)
  el[`on${event}`] = handler
}

// 统一封装的 fetch 工具
const http = {
  get: (url, params)  => fetch(`${url}?${new URLSearchParams(params)}`).then(r => r.json()),
  post: (url, data)   => fetch(url, { method: 'POST', body: JSON.stringify(data) }).then(r => r.json()),
  put: (url, data)    => fetch(url, { method: 'PUT', body: JSON.stringify(data) }).then(r => r.json()),
  delete: (url)       => fetch(url, { method: 'DELETE' }).then(r => r.json()),
}
优点 缺点
减少系统间依赖,提供简洁入口 不符合开闭原则,外观改变可能影响多处
提高安全性和易用性

10. 桥接模式

将抽象与实现分离,使它们可以独立变化。

核心思想:让"做什么"和"怎么做"各自独立,通过组合关联。

js 复制代码
// "怎么做" --- 颜色实现
const colors = {
  red:   () => ({ name: '红色', hex: '#ff0000' }),
  blue:  () => ({ name: '蓝色', hex: '#0000ff' }),
  green: () => ({ name: '绿色', hex: '#00ff00' }),
}

// "做什么" --- 形状抽象
const createShape = (type, color) => ({
  draw: () => console.log(`绘制一个${color.name}(${color.hex})的${type}`),
})

// 组合使用
const red = colors.red()
const circle = createShape('圆形', red)
circle.draw() // 绘制一个红色(#ff0000)的圆形

现实中的例子:

js 复制代码
// 渲染器 + 数据源 的桥接
// 图表库:renderer 可以在 Canvas/SVG 之间切换,dataProvider 可以在 REST/WebSocket 之间切换
const createChart = ({ renderer, dataProvider }) => ({
  render: async () => {
    const data = await dataProvider.fetch()
    renderer.draw(data)
  },
})
优点 缺点
抽象和实现可独立扩展 类/函数数量增加
提高可扩展性

11. 组合模式

将对象组合成树形结构,以表示"整体---部分"的层次关系,让客户端对单个对象和组合对象的使用保持一致。

js 复制代码
// 文件系统 --- 文件和文件夹都能"显示大小"
const createFile = (name, size) => ({
  name,
  getSize: () => {
    console.log(`${name}: ${size}KB`)
    return size
  },
})

const createFolder = (name) => {
  const children = []
  return {
    name,
    add: (item) => { children.push(item) },
    getSize: () => {
      const total = children.reduce((sum, c) => sum + c.getSize(), 0)
      console.log(`${name} 文件夹总计: ${total}KB`)
      return total
    },
  }
}

// 使用
const home = createFolder('home')
home.add(createFile('readme.md', 2))
home.add(createFile('package.json', 5))

const src = createFolder('src')
src.add(createFile('index.js', 20))
src.add(createFile('utils.js', 10))
home.add(src)

home.getSize()
// readme.md: 2KB
// package.json: 5KB
// index.js: 20KB
// utils.js: 10KB
// src 文件夹总计: 30KB
// home 文件夹总计: 37KB

现实中的例子:

js 复制代码
// React 组件树 ------ 每个组件都是"可渲染"的,无论是叶子组件还是组合组件
const Button = (props) => render('button', props)
const Form = (props) => (
  render('form', null,
    props.children.map(child => child()) // 统一处理!
  )
)
优点 缺点
客户端统一处理单个对象和组合对象 对象太多时系统可能负担较重
易于扩展,新增类型无需改动使用方

12. 享元模式

运用共享技术有效支持大量细粒度对象的复用,减少内存占用。

核心思想 :把大量相似对象的共享状态 (内部状态)抽出来共用,非共享状态(外部状态)在使用时传入。

js 复制代码
// 驾考车 --- 只创建有限的几辆车,多考生排队复用
const createExamCarPool = (manualCount, autoCount) => {
  const cars = []
  let id = 0

  // 创建车辆(享元)
  const createCar = (type) => {
    const carId = ++id
    return {
      carId,
      type,
      examine: (candidateId) => new Promise((resolve) => {
        console.log(`考生-${candidateId} 在${type}车-${carId} 上考试`)
        setTimeout(() => {
          console.log(`考生-${candidateId} 考试完毕`)
          resolve()
        }, Math.random() * 2000 + 500)
      }),
    }
  }

  // 初始化车辆池
  for (let i = 0; i < manualCount; i++) cars.push(createCar('手动档'))
  for (let i = 0; i < autoCount; i++) cars.push(createCar('自动档'))

  // 分配车辆:找空闲车
  const queue = []
  const assign = (candidateId) => {
    const car = cars.find(c => !c.busy)
    if (car) {
      car.busy = true
      car.examine(candidateId).then(() => {
        car.busy = false
        if (queue.length) assign(queue.shift())  // 考完立即分配给下一个人
      })
    } else {
      queue.push(candidateId)
    }
  }

  return { assign, queue }
}

// 3 辆车,10 个考生
const pool = createExamCarPool(2, 1)
for (let i = 1; i <= 10; i++) pool.assign(i)
优点 缺点
大幅减少对象数量,节省内存 需要区分内部/外部状态,增加复杂度
外部状态管理不当容易混乱

适用场景:大量相似对象导致内存紧张时(如在线表格的单元格、游戏中的粒子、文件上传列表)。


三、行为型模式

13. 观察者模式(发布---订阅)

定义一对多的依赖关系,当一个对象状态变化时,所有依赖者自动收到通知。

js 复制代码
// 股票行情 --- 一个价格源,多个观察者
const createStock = (symbol, initPrice) => {
  const observers = new Set()
  let price = initPrice

  return {
    subscribe: (fn) => {
      observers.add(fn)
      return () => observers.delete(fn)  // 返回取消订阅函数
    },
    getPrice: () => price,
    setPrice: (newPrice) => {
      if (newPrice !== price) {
        price = newPrice
        observers.forEach(fn => fn(price)) // 通知所有观察者
      }
    },
  }
}

// 使用
const appleStock = createStock('AAPL', 150)

const unsubscribe = appleStock.subscribe((price) => {
  console.log(`📊 收到更新:AAPL 现价 $${price}`)
})

appleStock.setPrice(152)  // 📊 收到更新:AAPL 现价 $152
unsubscribe()             // 取消订阅
appleStock.setPrice(155)  // 无输出

现实中的例子:

js 复制代码
// DOM 事件
document.addEventListener('click', () => console.log('clicked'))

// Vue 响应式 / EventEmitter / RxJS
// Redux store.subscribe()
const store = {
  listeners: [],
  subscribe: (fn) => { store.listeners.push(fn); return () => store.listeners = store.listeners.filter(l => l !== fn) },
  dispatch: (action) => store.listeners.forEach(fn => fn(action)),
}
优点 缺点
广播通信,对象间解耦 过度使用会让调用链路难以追踪
灵活,可动态增减观察者 通知顺序可能不可控

14. 策略模式

定义一系列算法,封装起来使它们可以互相替换。算法的变化不会影响使用算法的客户端。

js 复制代码
// 电商折扣计算 --- 不同折扣策略
const discountStrategies = {
  normal:    (price) => price,
  vip:       (price) => price * 0.9,
  superVip:  (price) => price * 0.8,
  seasonal:  (price) => price * 0.75,
}

const calcPrice = (price, strategy) =>
  (discountStrategies[strategy] || discountStrategies.normal)(price)

// 使用
console.log(calcPrice(100, 'vip'))       // 90
console.log(calcPrice(100, 'seasonal'))  // 75

现实中的例子 --- 表单校验:

js 复制代码
const validateStrategies = {
  required: (val) => val.trim() ? '' : '必填',
  minLen: (len) => (val) => val.length >= len ? '' : `至少${len}个字符`,
  isEmail: (val) => /^[\w-]+@[\w-]+\.[\w-]+$/.test(val) ? '' : '邮箱格式不正确',
  isMobile: (val) => /^1[3-9]\d{9}$/.test(val) ? '' : '手机号格式不正确',
}

// 校验工厂
const createValidator = (rules) => (value) => {
  for (const [strategy, param] of rules) {
    const fn = typeof param === 'function' ? param : validateStrategies[strategy](param)
    const error = fn(value)
    if (error) return error
  }
  return ''
}

// 使用
const validateName = createValidator([
  ['required'],
  ['minLen', 2],
])
console.log(validateName(''))   // 必填
console.log(validateName('张')) // 至少2个字符
优点 缺点
避免多重 if-else / switch 策略类/函数增多
符合开闭原则,算法可灵活替换 使用者需要了解各策略的区别

15. 模板方法模式

在一个方法中定义算法的骨架,将一些步骤延迟到子函数中实现。

js 复制代码
// 饮品制作模板 --- 步骤固定,具体实现可变
const makeDrink = ({ brew, addFlavor }) => () => {
  // 这些步骤是固定的
  const boilWater = () => console.log('① 烧开水')
  const pourIntoCup = () => console.log('③ 倒进杯子')

  boilWater()
  brew()         // 可变步骤 1
  pourIntoCup()
  addFlavor()    // 可变步骤 2
  console.log('✅ 制作完成\n')
}

// 咖啡
const makeCoffee = makeDrink({
  brew:      () => console.log('② 冲泡咖啡粉'),
  addFlavor: () => console.log('④ 加牛奶和糖'),
})

// 茶
const makeTea = makeDrink({
  brew:      () => console.log('② 浸泡茶叶'),
  addFlavor: () => console.log('④ 加柠檬'),
})

makeCoffee()
makeTea()

现实中的例子:

js 复制代码
// Vue/React 生命周期钩子 ------ 框架定义了渲染流程(模板方法),
// 你在 mounted/componentDidMount 里填充自己的逻辑
// Axios 拦截器也是模板方法的体现
优点 缺点
公共代码集中维护,避免重复 步骤固定,灵活性受限于模板定义
子函数只需关心自己那部分

16. 职责链模式

将请求沿链传递,直到有对象处理它为止。

js 复制代码
// 请假审批链:组长(≤1天) → 经理(≤3天) → 总监(>3天)
const createApprover = (name, limit, next = null) => ({
  handle: (days) => {
    if (days <= limit) {
      console.log(`${name} 批准了 ${days} 天假期`)
      return true
    }
    if (next) return next.handle(days)
    console.log(`无人能批准 ${days} 天假期`)
    return false
  },
})

const director = createApprover('总监', 10)
const manager  = createApprover('经理', 3, director)
const leader   = createApprover('组长', 1, manager)

leader.handle(1)  // 组长 批准了 1 天假期
leader.handle(2)  // 经理 批准了 2 天假期
leader.handle(5)  // 总监 批准了 5 天假期

现实中的例子:

js 复制代码
// DOM 事件冒泡 --- 点击 li,事件沿 li → ul → div → body 传递
// Koa/Express 中间件 --- 请求依次经过各中间件,某个中间件处理后就返回
// JS 原型链 --- 属性查找沿 __proto__ 向上
优点 缺点
请求发送者和接收者解耦 不能保证请求一定被处理
可动态调整链的顺序和成员 长链可能带来性能问题

17. 命令模式

将请求封装成对象,使你可以用不同的请求参数化客户端,支持排队、日志、撤销。

js 复制代码
// 遥控器 --- 每个按钮是一个命令
const createTV = () => {
  let on = false
  let volume = 10

  return {
    turnOn: () => { on = true; console.log('📺 电视开了') },
    turnOff: () => { on = false; console.log('📺 电视关了') },
    volumeUp: () => { volume++; console.log(`🔊 音量:${volume}`) },
    volumeDown: () => { volume = Math.max(0, volume - 1); console.log(`🔉 音量:${volume}`) },
  }
}

// 命令调度器 --- 支持撤销
const createRemote = (tv) => {
  const history = []

  const execute = (command) => {
    const [action, undo] = command
    action()
    history.push({ action, undo })
  }

  const undo = () => {
    if (!history.length) return console.log('没有可撤销的操作')
    const cmd = history.pop()
    cmd.undo()
  }

  return { execute, undo }
}

// 定义命令(执行 + 撤销 配对)
const commands = (tv) => ({
  on:   [() => tv.turnOn(),  () => tv.turnOff()],
  off:  [() => tv.turnOff(), () => tv.turnOn()],
  up:   [() => tv.volumeUp(), () => tv.volumeDown()],
  down: [() => tv.volumeDown(), () => tv.volumeUp()],
})

const tv = createTV()
const remote = createRemote(tv)
const cmd = commands(tv)

remote.execute(cmd.on)   // 📺 电视开了
remote.execute(cmd.up)   // 🔊 音量:11
remote.execute(cmd.up)   // 🔊 音量:12
remote.undo()            // 🔉 音量:11
remote.undo()            // 🔊 音量:10
优点 缺点
命令和执行者解耦 命令类/对象增多
支持撤销、重做、命令队列

适用场景:编辑器撤销/重做、任务队列、宏命令录制。


18. 状态模式

对象内部状态改变时,其行为也随之改变。看起来就像换了类一样。

js 复制代码
// 交通灯 --- 状态切换逻辑
const createTrafficLight = () => {
  const states = {
    red: {
      color: '🔴',
      duration: 3000,
      next: () => 'green',
    },
    green: {
      color: '🟢',
      duration: 3000,
      next: () => 'yellow',
    },
    yellow: {
      color: '🟡',
      duration: 1000,
      next: () => 'red',
    },
  }

  let current = 'red'
  let timer = null

  const run = () => {
    const state = states[current]
    console.log(`${state.color} ${current.toUpperCase()} --- 持续${state.duration / 1000}秒`)
    timer = setTimeout(() => {
      current = state.next()
      run()
    }, state.duration)
  }

  const stop = () => clearTimeout(timer)

  return { run, stop, getState: () => current }
}

// 使用:
// const light = createTrafficLight()
// light.run()  // 🔴 → 🟢 → 🟡 → 🔴 ...

现实中的例子:

js 复制代码
// Promise 的 pending → fulfilled / rejected 状态转换
// 订单状态:待付款 → 已付款 → 已发货 → 已收货 → 已完成
const createOrder = () => {
  const states = {
    pending:   { pay: () => 'paid', cancel: () => 'cancelled' },
    paid:      { ship: () => 'shipped', refund: () => 'refunding' },
    shipped:   { receive: () => 'completed' },
    completed: {},
    cancelled: {},
  }

  let state = 'pending'
  return {
    getState: () => state,
    transition: (action) => {
      const next = states[state]?.[action]
      if (!next) return console.log(`❌ 当前[${state}]不能执行[${action}]`)
      state = next
      console.log(`✅ ${action} → ${state}`)
    },
  }
}
优点 缺点
消除大量 if-else,状态逻辑清晰 状态类变多
状态切换一目了然

19. 迭代器模式

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

js 复制代码
// 自定义迭代器
const createIterator = (arr) => {
  let index = 0
  return {
    next: () => (index < arr.length
      ? { value: arr[index++], done: false }
      : { value: undefined, done: true }
    ),
  }
}

const it = createIterator([10, 20, 30])
let result = it.next()
while (!result.done) {
  console.log(result.value) // 10, 20, 30
  result = it.next()
}

现实中的例子:

js 复制代码
// ES6 内置:Array.prototype[Symbol.iterator]
// for...of 就是基于迭代器协议
// Generator 函数
const range = function* (start, end) {
  for (let i = start; i <= end; i++) yield i
}
for (const n of range(1, 5)) console.log(n) // 1, 2, 3, 4, 5
优点 缺点
统一遍历接口,不暴露内部结构 简单遍历使用 forEach 更直接
支持不同数据结构用同一套算法

20. 备忘录模式

在不破坏封装的前提下,捕获对象内部状态并在外部保存,以便后续恢复。

js 复制代码
// 文本编辑器 --- 支持撤销
const createEditor = () => {
  let content = ''
  const history = []  // 备忘录栈
  let cursor = -1

  const save = () => {
    // 保存当前状态
    history.length = cursor + 1  // 清除"未来"历史
    history.push(content)
    cursor++
  }

  return {
    type: (text) => {
      save()
      content += text
      console.log(`📝 当前内容: "${content}"`)
    },
    undo: () => {
      if (cursor <= 0) return console.log('无法撤销')
      cursor--
      content = history[cursor]
      console.log(`↩ 撤销后: "${content}"`)
    },
    redo: () => {
      if (cursor >= history.length - 1) return console.log('无法重做')
      cursor++
      content = history[cursor]
      console.log(`↪ 重做后: "${content}"`)
    },
    getContent: () => content,
  }
}

const editor = createEditor()
editor.type('Hello')
editor.type(' World')
editor.undo()       // "Hello"
editor.undo()       // ""
editor.redo()       // "Hello"
优点 缺点
提供状态恢复机制 保存大量状态会占用内存
不破坏封装性

适用场景:编辑器撤销/重做、游戏存档、表单草稿保存。


21. 中介者模式

用一个中介对象封装一系列对象之间的交互,使各对象不需要显式地相互引用。

js 复制代码
// 聊天室 --- 中介者转发消息,用户之间不直接通信
const createChatRoom = () => {
  const users = {}

  return {
    register: (user) => { users[user.name] = user; user.setRoom(this) },
    send: (from, to, msg) => {
      if (to) {
        // 私聊
        users[to]?.receive(from, msg)
      } else {
        // 广播
        Object.keys(users).forEach(name => {
          if (name !== from) users[name].receive(from, msg)
        })
      }
    },
  }
}

const createUser = (name) => {
  let room = null
  return {
    name,
    setRoom: (r) => { room = r },
    send: (to, msg) => room.send(name, to, msg),
    receive: (from, msg) => console.log(`[${name}] 收到 ${from}: ${msg}`),
  }
}

const room = createChatRoom()
const alice = createUser('Alice')
const bob = createUser('Bob')
const carol = createUser('Carol')

room.register(alice)
room.register(bob)
room.register(carol)

alice.send('', '大家好!')  // 广播
bob.send('Alice', '你好 Alice')  // 私聊
优点 缺点
网状多对多 → 一对多,降低耦合 中介者本身可能变得臃肿
各对象独立,易于复用

22. 访问者模式

在不改变元素类的前提下,定义作用于元素的新操作。

js 复制代码
// 对不同的员工类型执行不同的操作(计算薪资、生成报表)
const employeeTypes = {
  engineer: (name, salary) => ({
    type: 'engineer',
    name,
    salary,
    accept: (visitor) => visitor.visitEngineer({ name, salary }),
  }),
  manager: (name, salary, bonus) => ({
    type: 'manager',
    name,
    salary,
    bonus,
    accept: (visitor) => visitor.visitManager({ name, salary, bonus }),
  }),
}

// 访问者:计算年终奖
const bonusCalculator = {
  visitEngineer: (e) => e.salary * 2,
  visitManager: (m) => m.salary * 3 + m.bonus,
}

// 访问者:生成工资条
const payslipGenerator = {
  visitEngineer: (e) => `${e.name}(工程师): 月薪 ${e.salary}`,
  visitManager: (m) => `${m.name}(经理): 月薪 ${m.salary} + 奖金 ${m.bonus}`,
}

// 使用
const alice = employeeTypes.engineer('Alice', 15000)
const bob = employeeTypes.manager('Bob', 20000, 5000)

console.log(alice.accept(bonusCalculator))   // 30000
console.log(bob.accept(bonusCalculator))     // 65000
console.log(alice.accept(payslipGenerator))  // Alice(工程师): 月薪 15000
优点 缺点
数据结构和操作分离,扩展操作方便 元素需要暴露内部信息给访问者
符合单一职责 新增元素类型时需要修改所有访问者

23. 解释器模式

定义一种语言的文法表示,并提供一个解释器来解释这种语言中的句子。

js 复制代码
// 简易计算器解释器 --- 解析 "数字 操作符 数字"
const tokenize = (input) => {
  const tokens = []
  const regex = /\d+|[+\-*/]/g
  let match
  while ((match = regex.exec(input)) !== null) tokens.push(match[0])
  return tokens
}

const interpret = (tokens) => {
  // 数字解析
  const number = (token) => {
    const num = parseInt(token, 10)
    if (isNaN(num)) throw new Error(`"${token}" 不是有效数字`)
    return num
  }

  // 操作符解析
  const operators = {
    '+': (a, b) => a + b,
    '-': (a, b) => a - b,
    '*': (a, b) => a * b,
    '/': (a, b) => {
      if (b === 0) throw new Error('除数不能为 0')
      return a / b
    },
  }

  const operator = (token) => {
    const fn = operators[token]
    if (!fn) throw new Error(`未知运算符: ${token}`)
    return fn
  }

  // 执行:左 → 右,不考虑优先级,以演示为目的
  let result = number(tokens[0])
  for (let i = 1; i < tokens.length; i += 2) {
    const op = operator(tokens[i])
    const num = number(tokens[i + 1])
    result = op(result, num)
  }
  return result
}

// 使用
const calculate = (expr) => interpret(tokenize(expr))
console.log(calculate('3 + 5 * 2')) // 16(先算 3+5=8,再算 8*2=16,无优先级)
console.log(calculate('10 / 2 + 3')) // 8
优点 缺点
易于扩展语法规则 复杂文法时性能较差
复杂文法难以维护,应考虑用现成的解析器

适用场景:SQL 解析、正则表达式、模板引擎、自定义 DSL。


模式对比速查表

对比项 模式 A 模式 B 核心区别
创建对象 工厂 建造者 工厂一步创建;建造者分步构建
创建对象 工厂 抽象工厂 工厂造一种产品;抽象工厂造一族产品
包装对象 适配器 代理 适配器改接口;代理不改接口
包装对象 代理 装饰器 代理控制访问;装饰器增强功能
解耦 观察者 中介者 观察者一对多广播;中介者一对多集中管理
解耦 策略 状态 策略外部选择算法;状态内部切换行为
结构 组合 享元 组合组织树形结构;享元共享实例减少内存

总结 :设计模式不是银弹,而是工具箱。关键是在合适场景选用合适的模式,不要为了用模式而用模式。理解每种模式要解决的问题,比记住它的写法更重要。

相关推荐
wangruofeng8 分钟前
2026 Web 技术选型指南,AI 时代如何选择适合项目的技术栈
前端·aigc·ai编程
\光辉岁月/13 分钟前
3.包装类
java·开发语言·包装类
honkun613 分钟前
vue 表格组件 vxe-table 修改滚动条样式,修改滚动条样式,统一宽度、颜色与自动隐藏
前端·javascript·vue.js
爱勇宝29 分钟前
研究数字孪生的这半年,做了个 Demo,也有了一些新想法
前端·程序员
不可能片场1 小时前
工程实践 一个缓存把选片从十分钟压到一分钟
前端·electron
前端小万1 小时前
探索副业五个月,最终以放弃告终
前端·后端·面试
FRing031 小时前
JavaScript入门到精通二十四(正则表达式、元字符、修饰符)
开发语言·前端·javascript·正则表达式
西索斯coding1 小时前
Grok 4.7 调用一直 401 怎么办?不是 Key 过期,是 Authorization 头格式问题——排查全流程 + 修复示例
服务器·开发语言·ai·php
葡萄城技术团队1 小时前
在 SpreadJS 里新增右键菜单,把业务动作放到用户手边
前端
大明二代2 小时前
从 RAG 到有记忆的 Agent:Mastra Memory 与 PostgreSQL 实战
前端·javascript·人工智能