前端框架设计模式详解 | 青训营笔记

54 阅读4分钟

设计模式是软件开发领域中的重要概念,它们提供了一种通用的解决方案来解决特定类别的问题,它们是从经验中总结出来的,可以帮助我们更好的组织和管理代码,提高代码的可维护性、可扩展性和可重用性。在前端开发中,这些模式同样适用,以下是一些常见的前端设计模式及其应用:

  • 单例模式

    单例模式确保一个类只有一个实例,并提供一个全局访问点来访问这个实例。在前端中,常用于管理全局状态、配置信息等场景。例如,一个应用程序可能只有一个全局的logger对象用于记录日志,或者只有一个ThemeManager来管理整个页面的主题切换。

    1. 我们可以使用闭包简单实现单例模式:
    const Singleton = (function(){
        let instance;
        function createInstance(){
            return {
              //这里定义单例对象的属性和方法
              log: function(message){
                console.log(message);
              }
            };
        }
        return {
          getInstance: function(){
            if(!instance){
              instance = createInstance();
            }
            return instance;
          }
        };
    })();
    
    const singleton1 = Singleton.getInstance();
    const singleton2 = Singleton.getInstance();
    console.log(singleton1 === singleton2);  //true 说明获取的是同一个实例
    
    1. 使用场景

      • 全局状态管理:在React应用中,使用Redux或MobX等状态管理库时,其内部的store往往可以看作是单例模式的体现。整个应用只有一个store来存放和管理应用的状态,各个组件可以通过相应的机制(如 React Redux 的 connect 函数或者 MobX 的 observer 等)来访问和修改这个唯一的状态源。
      • Window全局对象、 缓存、构造函数、静态方法getInstance()、ES6模块Module等。
    2. 优点

      • 节省内存:避免创建多个相同功能的实例,对于占用较多内存资源的对象(如复杂的配置对象)可以节省一些空间。
      • 全局访问方便:在代码的不同地方都能方便地获取到这个唯一实例,利于统一管理和协调相关操作,比如统一的状态更新。
    3. 缺点

      • 违反单一职责原则:单例类可能承担过多的职责,因为它要在整个应用中作为唯一的实例提供服务,导致类的功能过于复杂,不宜维护和测试。
      • 依赖隐藏问题:由于全局可访问,代码中地依赖关系不够清晰,不利于理解代码的整体结构,增加了耦合度。
  • 发布-订阅模式(观察者模式的一种变体)

    发布-订阅模式建立了一种消息传递机制,存在发布者、订阅者和消息中心(也叫事件总线)三个角色,用于解耦生产者(发布者)和消费者(订阅者)之间的关系。发布者将消息发布到消息中心,而订阅者向消息中心订阅感兴趣的消息类型,当消息中心收到相应消息后,会通知所有订阅了该消息的订阅者进行相应处理。

    1. 代码示例
    class EventBus {
       constructor(){
          this.events = {};
       }
       on(eventName, callback){
          if(!this.events[eventName]){
             this.events[eventName] = [];
          }
          this.events[eventName].push(callback);
       }
       emit(eventName, ...args){
          const callbacks = this.events[eventName];
          if(callbacks){
             callbacks.forEach(callback => callback(...args));
          }
       }
       off(eventName, callback){
          const callbacks = this.events[eventName];
          if(callbacks){
             const index= callbacks.indexOf(callback);
             if(index !== -1){
                callbacks.splice(index, 1);
             }
          }
       }
    }
    
    const eventBus = new EventBus();
    
    const callback1 = function(data){
       console.log(`callback 1 received data: ${data}`);
    }
     const callback2 = function(data){
       console.log(`callback 2 received data: ${data}`);
    }
    
    eventBus.on('newData', callback1);
    eventBus.on('newData', callback2);
    
    eventBus.emit('newData','Some important data');
    
    eventBus.off('newData', callback1);
    eventBus.emit('newData', 'Another data');
    
    1. 应用场景

      • 组件间通信:在大型的Vue或React项目中,不同层级,不同位置的组件之间可能需要传递数据或触发相应操作。比如一个侧边栏组件的展开和收缩状态变化(作为发布者发布sidebarToggle消息),而顶部导航栏组件和内容区域组件可能都关心这个状态变化,以便做出相应的界面调整,这时就可以通过发布-订阅模式借助全局的事件总线来实现通信。

      • 状态监测与更新、事件驱动架构等。

    2. 优点

      • 解耦性极强:发布者和订阅者不需要直接知道对方的存在,它们只与消息中心交互,降低了模块之间的耦合度,方便模块的独立开发、测试以及替换,只要遵循统一的消息发布和订阅规范即可。
      • 方便实现一对多的通信:一个消息可以有多个订阅者,可以很或灵活的应对需要多模块响应同一个事件的场景。
    3. 缺点

      • 可能导致代码难以跟踪调试:由于消息的传递是间接的,当出现问题时,很难直观地看出是哪个发布者发出了消息,或者哪个订阅者对消息的处理出现了异常,需要查看消息中心的订阅和发布记录以及相关的回调函数逻辑来排查问题。
      • 内存管理需要注意:如果订阅者没有正确地取消订阅(尤其是在组件销毁等场景下),可能会导致内存泄漏,因为消息中心仍然保留着对这些订阅者回调函数的引用,使得相关对象无法被垃圾回收机制回收。