前端常用设计模式与场景

单例、观察者、策略、代理等设计模式在前端的实现与应用

问题

前端开发中常用哪些设计模式?各自的使用场景是什么?

解答

1. 单例模式

确保一个类只有一个实例。

// 单例模式
class Store {
  static instance = null
  
  constructor() {
    if (Store.instance) {
      return Store.instance
    }
    this.state = {}
    Store.instance = this
  }
  
  setState(key, value) {
    this.state[key] = value
  }
  
  getState(key) {
    return this.state[key]
  }
}

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

场景:全局状态管理、弹窗组件、登录框

2. 发布订阅模式

对象间一对多的依赖关系,状态变化时通知所有订阅者。

class EventEmitter {
  constructor() {
    this.events = {}
  }
  
  // 订阅
  on(event, callback) {
    if (!this.events[event]) {
      this.events[event] = []
    }
    this.events[event].push(callback)
    return this
  }
  
  // 发布
  emit(event, ...args) {
    const callbacks = this.events[event]
    if (callbacks) {
      callbacks.forEach(cb => cb(...args))
    }
    return this
  }
  
  // 取消订阅
  off(event, callback) {
    const callbacks = this.events[event]
    if (callbacks) {
      this.events[event] = callbacks.filter(cb => cb !== callback)
    }
    return this
  }
  
  // 只订阅一次
  once(event, callback) {
    const wrapper = (...args) => {
      callback(...args)
      this.off(event, wrapper)
    }
    this.on(event, wrapper)
    return this
  }
}

// 使用
const emitter = new EventEmitter()
emitter.on('login', user => console.log(`${user} 登录了`))
emitter.emit('login', '张三') // 张三 登录了

场景:组件通信、事件总线、Vue 的 $emit

3. 策略模式

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

// 表单验证策略
const strategies = {
  required(value, message) {
    return value.trim() === '' ? message : ''
  },
  minLength(value, length, message) {
    return value.length < length ? message : ''
  },
  mobile(value, message) {
    return !/^1[3-9]\d{9}$/.test(value) ? message : ''
  },
  email(value, message) {
    return !/^\w+@\w+\.\w+$/.test(value) ? message : ''
  }
}

// 验证器
class Validator {
  constructor() {
    this.rules = []
  }
  
  add(value, rule, ...args) {
    this.rules.push(() => strategies[rule](value, ...args))
    return this
  }
  
  validate() {
    for (const rule of this.rules) {
      const error = rule()
      if (error) return error
    }
    return ''
  }
}

// 使用
const validator = new Validator()
validator
  .add('', 'required', '用户名不能为空')
  .add('abc', 'minLength', 6, '用户名至少6位')

console.log(validator.validate()) // 用户名不能为空

场景:表单验证、支付方式选择、不同权限的操作

4. 代理模式

为对象提供一个代理,控制对原对象的访问。

// 图片懒加载代理
const lazyLoadImage = (function() {
  const img = document.createElement('img')
  
  return {
    setSrc(node, src) {
      // 先显示占位图
      node.src = 'loading.gif'
      // 加载真实图片
      img.onload = () => {
        node.src = src
      }
      img.src = src
    }
  }
})()

// 缓存代理
const memoize = (fn) => {
  const cache = new Map()
  
  return function(...args) {
    const key = JSON.stringify(args)
    if (cache.has(key)) {
      return cache.get(key)
    }
    const result = fn.apply(this, args)
    cache.set(key, result)
    return result
  }
}

// 使用
const fibonacci = memoize(n => {
  if (n <= 1) return n
  return fibonacci(n - 1) + fibonacci(n - 2)
})

console.log(fibonacci(40)) // 快速计算

场景:图片懒加载、缓存计算结果、Vue3 响应式(Proxy)

5. 装饰器模式

动态地给对象添加额外的职责。

// 函数装饰器 - 添加日志
function withLog(fn) {
  return function(...args) {
    console.log(`调用 ${fn.name},参数:`, args)
    const result = fn.apply(this, args)
    console.log(`返回值:`, result)
    return result
  }
}

// 函数装饰器 - 防抖
function debounce(fn, delay) {
  let timer = null
  return function(...args) {
    clearTimeout(timer)
    timer = setTimeout(() => {
      fn.apply(this, args)
    }, delay)
  }
}

// 使用
const add = (a, b) => a + b
const loggedAdd = withLog(add)
loggedAdd(1, 2)
// 调用 add,参数: [1, 2]
// 返回值: 3

// React HOC 也是装饰器模式
function withAuth(Component) {
  return function AuthComponent(props) {
    const isLogin = checkLogin()
    if (!isLogin) {
      return <Login />
    }
    return <Component {...props} />
  }
}

场景:React HOC、防抖节流、日志记录、权限校验

6. 工厂模式

封装创建对象的逻辑,不暴露具体实现。

// 简单工厂
class Button {
  constructor(type) {
    this.type = type
  }
  render() {
    return `<button class="${this.type}">按钮</button>`
  }
}

class Input {
  constructor(type) {
    this.type = type
  }
  render() {
    return `<input type="${this.type}" />`
  }
}

// 工厂函数
function createComponent(type, config) {
  switch(type) {
    case 'button':
      return new Button(config.style)
    case 'input':
      return new Input(config.inputType)
    default:
      throw new Error('未知组件类型')
  }
}

// 使用
const btn = createComponent('button', { style: 'primary' })
const input = createComponent('input', { inputType: 'text' })

场景:创建组件实例、根据配置生成不同对象

关键点

  • 单例模式:全局唯一实例,适合状态管理、弹窗
  • 发布订阅:解耦组件通信,是事件系统的基础
  • 策略模式:消除 if-else,算法可替换,适合表单验证
  • 代理模式:控制访问,适合懒加载、缓存、Vue3 响应式
  • 装饰器模式:不修改原函数增强功能,适合 HOC、防抖节流
  • 工厂模式:封装创建逻辑,适合根据配置创建不同实例