深入浅出JavaScript常见设计模式:从原理到实战(1)


深入浅出JavaScript常见设计模式:从原理到实战(1)

设计模式是一种在特定情境下解决软件设计中常见问题的通用方案或模板。在特定的开发场景中使用特定的设计模式,可以提升代码质量,增强代码可读性和可维护性,提高团队开发效率,降低软件设计的复杂度。本文将介绍前端开发中常用的设计模式,讲解他们的含义,核心特性,使用场景及注意事项


一、设计模式核心认知

1.1 什么是设计模式

  • 定义:经过验证的代码组织最佳实践方案
  • 黄金三角
    • 场景:特定问题的解决方案
    • 结构:类/对象的关系拓扑
    • 效果:可维护性/扩展性提升

1.2 为什么需要设计模式

  • 代码腐化防控:减少面条式代码
  • 架构清晰度:提高模块化程度(示例:React Hooks vs Class组件)
  • 团队协作:统一代码交流语言

二、4大高频设计模式详解

2.1 单例模式(Singleton)

单例模式(Singleton Pattern)是创建型设计模式中最基础且常用的模式之一,其核心目的是保证一个类仅有一个实例,并提供一个全局访问点供其他对象使用。以下是其关键要点:


核心特性
特性 说明
唯一实例 单例类在任何情况下只能生成一个对象实例
自我创建 单例类需自行创建该唯一实例,通常通过私有化构造函数实现
全局访问 通过静态方法(如getInstance())提供全局访问入口
延迟初始化 实例仅在首次请求时创建(可选特性,避免资源浪费)

应用场景
  1. 系统配置管理

    • 全局配置文件读取(避免重复加载)
    • 应用主题/语言设置管理
  2. 资源管理器

    • 数据库连接池(控制连接数)
    • 打印任务队列(避免资源竞争)
  3. 状态管理

    • Redux/Vuex的Store实例
    • 用户登录会话管理
  4. 工具服务

    • 日志记录器(统一输出格式)
    • 性能监控服务(数据聚合)

代码实现(线程安全增强版)
javascript 复制代码
class DatabaseConnection {
  constructor(config) {
    if (DatabaseConnection.instance) {
      return DatabaseConnection.instance
    }
    
    // 初始化数据库连接
    this.connection = this.createConnection(config)
    DatabaseConnection.instance = this
  }

  // 私有方法(实际项目应使用Symbol实现)
  createConnection(config) {
    console.log('Creating new database connection...')
    return { 
      host: config.host,
      query: (sql) => console.log(`Executing: ${sql}`)
    }
  }

  // 静态访问器
  static getInstance(config) {
    if (!this.instance) {
      this.instance = new DatabaseConnection(config)
    }
    return this.instance
  }
}

// 使用示例
const config = { host: 'localhost:3306' }
const db1 = DatabaseConnection.getInstance(config)
const db2 = DatabaseConnection.getInstance(config)

console.log(db1 === db2) // true
db1.connection.query('SELECT * FROM users') // Executing: SELECT * FROM users

实现流程图解
注意事项
  1. 多线程环境

    • 浏览器中通过Web Workers实现多线程时需加锁(如Mutex
    javascript 复制代码
    // 伪代码示例
    if (navigator.locks) {
      navigator.locks.request('singleton_lock', async () => {
        return DatabaseConnection.getInstance()
      })
    }
  2. 测试困难

    • 单例状态会跨测试用例保留,解决方案:
    javascript 复制代码
    beforeEach(() => {
      DatabaseConnection.instance = null
    })
  3. 违反单一职责原则

    • 同时承担实例创建和业务逻辑,可通过代理模式拆分:
    javascript 复制代码
    class DatabaseSingleton {
      // 仅负责实例管理
    }
    
    class DatabaseService {
      // 处理具体业务逻辑
    }

现代框架中的演进
  1. React Context API

    javascript 复制代码
    // 创建单例上下文
    const AuthContext = React.createContext(null)
    
    // 全局访问点
    function useAuth() {
      return useContext(AuthContext)
    }
  2. Vue3的provide/inject

    javascript 复制代码
    // 创建单例服务
    const storageService = reactive(new StorageService())
    
    // 注入全局
    app.provide('storage', storageService)

模式延伸 :单例模式常与工厂模式结合使用,形成多例模式(Multiton),通过键值对管理多个受限实例:

javascript 复制代码
class LoggerMultiton {
  static instances = new Map()
  
  static getInstance(namespace) {
    if (!this.instances.has(namespace)) {
      this.instances.set(namespace, new Logger(namespace))
    }
    return this.instances.get(namespace)
  }
}
实现
javascript 复制代码
// 现代ES6实现(线程安全版)
class Logger {
  constructor() {
    if (!Logger.instance) {
      this.logs = [];
      Logger.instance = this;
    }
    return Logger.instance;
  }

  log(message) {
    this.logs.push(message);
    console.log(`[LOG]: ${message}`);
  }
}

// 使用示例
const logger1 = new Logger();
const logger2 = new Logger();
console.log(logger1 === logger2); // true
应用场景
  • 全局状态管理(Redux Store)
  • 浏览器环境对象(Window/Document)
  • 日志记录器

2.2 观察者模式(Observer)

观察者模式(Observer Pattern)是行为型设计模式 的经典实现,其核心目标是建立对象间的一对多依赖关系,当一个对象(被观察者)状态变化时,所有依赖它的对象(观察者)都会自动收到通知。以下是其关键要点:


核心特性
特性 说明
松耦合 观察者与被观察者之间通过接口交互,无需了解彼此具体实现
动态订阅 允许运行时动态添加/移除观察者
广播通知 状态变化时自动通知所有已注册的观察者
推拉模型 支持推送完整数据或由观察者主动拉取数据(默认推模式)

应用场景
  1. 事件驱动系统

    • GUI组件交互(如按钮点击事件)
    • 表单输入实时校验
  2. 实时数据同步

    • 股票行情实时更新
    • 多端数据同步(如协同编辑)
  3. 状态管理

    • Vue响应式系统(基于Watcher)
    • Redux中间件监听
  4. 异步任务协调

    • 文件上传进度通知
    • 批量数据处理状态跟踪

代码实现(支持防抖的增强版)
javascript 复制代码
class Subject {
  constructor() {
    this.observers = new Set()
    this.state = null
    this.debounceTimer = null
  }

  // 注册观察者(支持自动去重)
  subscribe(observer, options = { debounce: 0 }) {
    const wrapper = data => {
      if (options.debounce > 0) {
        clearTimeout(this.debounceTimer)
        this.debounceTimer = setTimeout(() => observer.update(data), options.debounce)
      } else {
        observer.update(data)
      }
    }
    
    this.observers.add({ 
      original: observer,
      wrapper
    })
  }

  // 移除观察者
  unsubscribe(observer) {
    for (const entry of this.observers) {
      if (entry.original === observer) {
        this.observers.delete(entry)
        break
      }
    }
  }

  // 状态更新(支持部分更新)
  setState(newState) {
    const prevState = this.state
    this.state = { 
      ...this.state, 
      ...(typeof newState === 'function' ? newState(prevState) : newState)
    }
    this.notify()
  }

  // 通知所有观察者
  notify() {
    this.observers.forEach(({ wrapper }) => wrapper(this.state))
  }
}

// 观察者基类
class Observer {
  update(data) {
    throw new Error('必须实现update方法')
  }
}

// 使用示例
class Logger extends Observer {
  update(data) {
    console.log('[Logger]', data)
  }
}

class Analytics extends Observer {
  update(data) {
    fetch('/api/analytics', {
      method: 'POST',
      body: JSON.stringify(data)
    })
  }
}

// 初始化主题
const userSubject = new Subject()

// 注册观察者(Logger立即响应,Analytics防抖500ms)
userSubject.subscribe(new Logger())
userSubject.subscribe(new Analytics(), { debounce: 500 })

// 触发状态更新
userSubject.setState({ name: 'Alice' })
userSubject.setState({ age: 30 }) // Analytics只会收到最终合并状态

实现流程图解

Subject Observer1 Observer2 subscribe() subscribe() setState() update(data) update(data) unsubscribe() setState() update(data) Subject Observer1 Observer2


注意事项
  1. 内存泄漏

    • 未及时取消订阅会导致观察者无法回收,解决方案:
    javascript 复制代码
    // 使用WeakMap存储弱引用
    const observers = new WeakMap()
    
    // 或使用AbortController(浏览器环境)
    const controller = new AbortController()
    subject.subscribe(observer, { signal: controller.signal })
    controller.abort() // 自动取消订阅
  2. 通知顺序

    • 观察者执行顺序不可控,关键业务需顺序保证:
    javascript 复制代码
    notify() {
      Array.from(this.observers)
        .sort((a,b) => a.priority - b.priority)
        .forEach(observer => observer.update())
    }
  3. 性能问题

    • 高频更新场景建议合并通知:
    javascript 复制代码
    class BatchedSubject extends Subject {
      notify() {
        if (!this.pending) {
          this.pending = true
          requestAnimationFrame(() => {
            super.notify()
            this.pending = false
          })
        }
      }
    }

现代框架中的演进
  1. RxJS Observable

    javascript 复制代码
    // 创建可观察对象
    const source = rxjs.fromEvent(document, 'click')
    
    // 订阅观察者
    const subscription = source.subscribe(e => console.log(e))
    
    // 取消订阅
    subscription.unsubscribe()
  2. Vue3的watchEffect

    javascript 复制代码
    const state = reactive({ count: 0 })
    
    // 自动依赖追踪
    const stop = watchEffect(() => {
      console.log('count:', state.count)
    })
    
    // 停止观察
    stop()
  3. React useEffect

    javascript 复制代码
    useEffect(() => {
      const subscription = dataStream.subscribe(handleData)
      return () => subscription.unsubscribe()
    }, [])

模式延伸:观察者模式与发布-订阅模式的核心差异:
直接通信 通过中间件 观察者模式 Subject-Observer 发布订阅模式 Event Channel

混合模式实践:结合代理模式实现安全观察:

javascript 复制代码
class ProtectedSubject extends Subject {
  subscribe(observer) {
    const proxy = new Proxy(observer, {
      get(target, prop) {
        if (prop === 'update') {
          return (...args) => {
            try {
              target[prop](...args)
            } catch (e) {
              console.error('Observer error:', e)
            }
          }
        }
        return target[prop]
      }
    })
    super.subscribe(proxy)
  }
}

2.3 策略模式(Strategy)

策略模式(Strategy Pattern)是行为型设计模式 的典型实现,其核心思想是定义一系列算法簇,使其可以相互替换,让算法的变化独立于使用它的客户端。以下是其关键要点:


核心特性
特性 说明
算法封装 每个策略类封装独立算法实现
动态切换 运行时可根据需求自由切换不同策略
消除条件分支 避免多重if-else或switch-case语句
开闭原则 新增策略无需修改已有代码

应用场景
  1. 业务规则引擎

    • 电商促销策略(满减、折扣、赠品)
    • 运费计算规则(按重量、体积、地区)
  2. 数据处理管道

    • 数据加密算法(AES、RSA、DES)
    • 图片压缩策略(WebP、JPEG、PNG)
  3. 游戏开发

    • 角色AI行为(攻击、防御、逃跑)
    • 物理碰撞检测(AABB、OBB、球体检测)
  4. 表单验证系统

    • 校验规则组合(必填、邮箱格式、长度限制)
    • 多国手机号验证策略

代码实现(支持策略链的增强版)
javascript 复制代码
// 策略接口定义
class ValidationStrategy {
  validate(value) {
    throw new Error('必须实现validate方法')
  }
}

// 具体策略实现
class RequiredStrategy extends ValidationStrategy {
  validate(value) {
    return !!value.trim()
  }
}

class EmailStrategy extends ValidationStrategy {
  validate(value) {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
  }
}

class MinLengthStrategy extends ValidationStrategy {
  constructor(min) {
    super()
    this.min = min
  }

  validate(value) {
    return value.length >= this.min
  }
}

// 策略上下文
class Validator {
  constructor() {
    this.strategies = []
    this.errors = []
  }

  addStrategy(strategy) {
    if (!(strategy instanceof ValidationStrategy)) {
      throw new Error('无效的策略类型')
    }
    this.strategies.push(strategy)
    return this // 支持链式调用
  }

  validate(value) {
    this.errors = this.strategies.map(strategy => 
      strategy.validate(value) ? null : strategy.constructor.name
    ).filter(Boolean)
    
    return {
      isValid: this.errors.length === 0,
      errors: this.errors
    }
  }
}

// 使用示例
const userValidator = new Validator()
  .addStrategy(new RequiredStrategy())
  .addStrategy(new MinLengthStrategy(6))

const emailValidator = new Validator()
  .addStrategy(new RequiredStrategy())
  .addStrategy(new EmailStrategy())

console.log(userValidator.validate('Alice'))  // { isValid: false, errors: ['MinLengthStrategy'] }
console.log(emailValidator.validate('test@')) // { isValid: false, errors: ['EmailStrategy'] }

实现流程图解

是 否 动态切换 客户端 创建Context 设置具体Strategy 执行算法 验证通过? 返回成功 返回失败 其他Strategy


注意事项
  1. 策略膨胀问题

    • 当策略超过10个时建议使用策略工厂:
    javascript 复制代码
    class StrategyFactory {
      static create(type, ...args) {
        const strategies = {
          required: RequiredStrategy,
          email: EmailStrategy,
          minLength: MinLengthStrategy
        }
        return new strategies[type](...args)
      }
    }
  2. 策略共享状态

    • 需要跨策略共享数据时使用上下文注入:
    javascript 复制代码
    class AdvancedStrategy extends ValidationStrategy {
      constructor(context) {
        super()
        this.context = context
      }
    }
  3. 性能考量

    • 高频调用场景建议缓存策略实例:
    javascript 复制代码
    const strategyCache = new Map()
    
    function getStrategy(type) {
      if (!strategyCache.has(type)) {
        strategyCache.set(type, new strategies[type]())
      }
      return strategyCache.get(type)
    }

现代框架中的演进
  1. React Hooks策略封装

    javascript 复制代码
    function useValidation(strategyType) {
      const [error, setError] = useState(null)
      
      const validate = useCallback((value) => {
        const strategy = StrategyFactory.create(strategyType)
        const isValid = strategy.validate(value)
        setError(isValid ? null : 'Invalid value')
        return isValid
      }, [strategyType])
    
      return { validate, error }
    }
  2. Vue3组合式API实现

    javascript 复制代码
    export function usePaymentStrategy(strategyType) {
      const strategies = {
        alipay: () => ({ pay: (amount) => /* 支付宝逻辑 */ }),
        wechat: () => ({ pay: (amount) => /* 微信支付逻辑 */ })
      }
    
      return reactive(strategies[strategyType]())
    }
  3. Node.js中间件策略

    javascript 复制代码
    const compressionStrategies = {
      gzip: require('compression'),
      brotli: require('compression/brotli')
    }
    
    app.use((req, res, next) => {
      const strategy = req.acceptsEncodings(['br', 'gzip'])
      return compressionStrategies[strategy]?.(req, res, next) || next()
    })

混合模式实践:策略模式+责任链实现复杂校验:

javascript 复制代码
class ValidationChain {
  constructor() {
    this.strategies = []
  }

  add(strategy) {
    this.strategies.push(strategy)
    return this
  }

  validate(value) {
    return this.strategies.reduce((result, strategy) => {
      if (!result.isValid) return result
      const isValid = strategy.validate(value)
      return {
        isValid,
        failedStrategy: isValid ? null : strategy.constructor.name
      }
    }, { isValid: true })
  }
}

最佳实践原则

  1. 策略粒度控制:单个策略应聚焦单一职责
  2. 无状态设计:优先使用纯函数策略实现
  3. 配置化策略:通过JSON配置动态加载策略
  4. 防御式编程:处理未知策略类型的降级方案

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

发布-订阅模式(Publish-Subscribe Pattern)是行为型设计模式 的进阶实现,其核心思想是通过事件通道解耦消息发布者与订阅者,实现跨组件/跨系统的异步通信。以下是其关键要点:


核心特性
特性 说明
完全解耦 发布者与订阅者互不感知,仅通过事件标识通信
异步通信 支持非阻塞式消息传递(基于事件循环)
多对多关系 单个事件可被多个订阅者消费,单个订阅者可监听多个事件
消息持久化 可选实现消息队列持久存储(如RabbitMQ、Kafka)

应用场景
  1. 分布式系统

    • 微服务间通信(订单服务 → 库存服务)
    • 实时数据分析管道
  2. 前端架构

    • 微前端应用通信(qiankun框架)
    • 组件间跨层级通信(非父子组件)
  3. 业务中台

    • 用户行为追踪系统(埋点数据收集)
    • 全站通知系统(公告/站内信广播)
  4. 物联网场景

    • 设备状态监控(传感器数据分发)
    • 智能家居联动(门锁开启 → 灯光调整)

代码实现(支持通配符的增强版)
javascript 复制代码
class EventBus {  
  constructor() {  
    this.channels = new Map();  
    this.wildcardChannels = new Map(); // 存储通配符订阅  
  }  

  // 支持 event.* 格式的通配符  
  subscribe(event, callback) {  
    if (event.includes('*')) {  
      const base = event.replace(/\*/, '');  
      const listeners = this.wildcardChannels.get(base) || new Set();  
      listeners.add(callback);  
      this.wildcardChannels.set(base, listeners);  
    } else {  
      const listeners = this.channels.get(event) || new Set();  
      listeners.add(callback);  
      this.channels.set(event, listeners);  
    }  
  }  

  // 支持一次性订阅  
  once(event, callback) {  
    const wrapper = (...args) => {  
      callback(...args);  
      this.unsubscribe(event, wrapper);  
    };  
    this.subscribe(event, wrapper);  
  }  

  unsubscribe(event, callback) {  
    if (event.includes('*')) {  
      const base = event.replace(/\*/, '');  
      const listeners = this.wildcardChannels.get(base);  
      if (listeners) listeners.delete(callback);  
    } else {  
      const listeners = this.channels.get(event);  
      if (listeners) listeners.delete(callback);  
    }  
  }  

  publish(event, data) {  
    // 精确匹配  
    this.channels.get(event)?.forEach(cb => cb(data));  

    // 通配符匹配  
    const [namespace] = event.split('.');  
    this.wildcardChannels.get(namespace + '.')?.forEach(cb => cb(data));  
  }  
}  

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

// 订阅精确事件  
bus.subscribe('order.created', (order) => {  
  console.log('处理新订单:', order);  
});  

// 订阅通配符事件  
bus.subscribe('order.*', (event) => {  
  console.log('订单状态变更:', event.type);  
});  

// 一次性订阅  
bus.once('user.login', (user) => {  
  console.log('欢迎首次登录:', user.name);  
});  

// 发布事件  
bus.publish('order.created', { id: 1, total: 100 });  
bus.publish('order.updated', { id: 1, status: 'shipped' });  

实现流程图解

Publisher EventBus SubscriberA SubscriberB subscribe('order.*') subscribe('order.created') publish('order.created', data) 触发回调 (通配符匹配) 触发回调 (精确匹配) publish('order.updated', data) 触发回调 (通配符匹配) Publisher EventBus SubscriberA SubscriberB


注意事项
  1. 内存泄漏防护

    • 使用 WeakRef + FinalizationRegistry 自动清理:
    javascript 复制代码
    const registry = new FinalizationRegistry(({ event, wrapper }) => {  
      eventBus.unsubscribe(event, wrapper);  
    });  
    
    function safeSubscribe(event, callback) {  
      const ref = new WeakRef(callback);  
      const wrapper = (data) => ref.deref()?.(data);  
      registry.register(callback, { event, wrapper });  
      eventBus.subscribe(event, wrapper);  
    }  
  2. 消息顺序保障

    • 添加优先级队列:
    javascript 复制代码
    class PriorityEventBus extends EventBus {  
      subscribe(event, callback, priority = 0) {  
        super.subscribe(event, { callback, priority });  
        // 按priority排序存储  
      }  
    }  
  3. 错误隔离机制

    • 防止单个订阅者崩溃影响全局:
    javascript 复制代码
    publish(event, data) {  
      this.channels.get(event)?.forEach(cb => {  
        try {  
          cb(data);  
        } catch (e) {  
          console.error('事件处理异常:', e);  
        }  
      });  
    }  

现代框架中的演进
  1. Vue3的provide/inject事件总线

    javascript 复制代码
    // 创建Symbol标识的事件类型  
    const ORDER_EVENT = Symbol('order');  
    
    // 提供事件发射器  
    provide(ORDER_EVENT, {  
      emit: (type, data) => bus.publish(`order.${type}`, data),  
      on: (type, callback) => bus.subscribe(`order.${type}`, callback)  
    });  
    
    // 注入使用  
    const { emit, on } = inject(ORDER_EVENT);  
  2. React的useSyncExternalStore

    javascript 复制代码
    function useEventStore(event) {  
      const [data, setData] = useState(null);  
    
      useEffect(() => {  
        const handler = (payload) => setData(payload);  
        bus.subscribe(event, handler);  
        return () => bus.unsubscribe(event, handler);  
      }, [event]);  
    
      return data;  
    }  
  3. Node.js的EventEmitter

    javascript 复制代码
    const { EventEmitter } = require('events');  
    const emitter = new EventEmitter();  
    
    // 异步监听处理  
    emitter.on('log', async (message) => {  
      await writeToFile(message);  
    });  
    
    // 错误处理  
    emitter.on('error', (err) => {  
      console.error('Event Error:', err);  
    });  

模式延伸
  1. 与中介者模式结合

    发布事件 订阅事件 作为中介者 路由事件 组件A 事件总线 组件B 组件C 其他服务

  2. 领域事件设计

    javascript 复制代码
    class DomainEvent {  
      constructor(type, payload) {  
        this.id = uuidv4();  
        this.timestamp = Date.now();  
        this.type = type;  
        this.payload = Object.freeze(payload);  
      }  
    }  
    
    // 发布领域事件  
    bus.publish('domain', new DomainEvent('OrderPaid', { orderId: 123 }));  
  3. 消息回溯实现

    javascript 复制代码
    class ReplayEventBus extends EventBus {  
      constructor() {  
        super();  
        this.history = new Map();  
      }  
    
      publish(event, data) {  
        super.publish(event, data);  
        const records = this.history.get(event) || [];  
        records.push({ timestamp: Date.now(), data });  
        this.history.set(event, records.slice(-100)); // 保留最近100条  
      }  
    
      replay(event, callback) {  
        this.history.get(event)?.forEach(record => callback(record.data));  
      }  
    }  

架构级应用:在微前端场景下的跨应用通信方案

javascript 复制代码
// 主应用注册全局总线  
window.globalEventBus = new EventBus();  

// 子应用A发布事件  
window.globalEventBus.publish('cart/update', { count: 5 });  

// 子应用B订阅事件  
window.globalEventBus.subscribe('cart/update', (payload) => {  
  document.getElementById('cart-counter').textContent = payload.count;  
});  

三、设计模式黄金法则

  1. 不滥用原则:避免过度设计(YAGNI原则)
  2. 场景适配:根据业务复杂度选择模式
  3. 模式组合:多数场景需要多模式配合
  4. 模式演进:现代框架已内置模式实现

四、下期预告

总共有23种经典的设计模式,本篇文章介绍了4种前端开发中常用的设计模式,其他设计模式将在下篇文章中继续分享哦,敬请期待~

相关推荐
kovli3 分钟前
红宝书第十七讲:通俗详解JavaScript的Promise与链式调用
前端·javascript
李是啥也不会9 分钟前
Vue中Axios实战指南:高效网络请求的艺术
前端·javascript·vue.js
jerry60910 分钟前
c++流对象
开发语言·c++·算法
fmdpenny11 分钟前
用python写一个相机选型的简易程序
开发语言·python·数码相机
贾公子20 分钟前
MySQL数据库基础 === 约束
前端·javascript
贾公子33 分钟前
element ui & plus 版本 日期时间选择器的差异
前端·javascript
贾公子38 分钟前
form组件的封装(element ui ) 简单版本
前端·javascript
贾公子39 分钟前
下拉框组件的封装(element ui )
前端·javascript
贾公子40 分钟前
ElementUI,在事件中传递自定义参数的两种方式
前端·javascript
贾公子41 分钟前
基于Vue3 + Typescript 封装 Element-Plus 组件
前端·javascript