设计模式在前端开发中的实践(十二)——单例模式

单例模式

单例模式是前端开发中常用的设计模式,单例模式本身也比较简单,因此对于很多同学来说,掌握度也是比较高的。

根据我7年的开发经验,我大致总结出单例模式2种高频使用场景:第一种场景是不需要用户创造它的实例对象,直接供用户调用,这种场景基本上都是用在封装工具类。第二种场景是需要用一些行为进行限制,比如如果一个界面上出现多个相同的UI组件(而逻辑上只出现一个才是合理的)就是明显的bug,但是为了降低调用者的心智负担,我们在设计的API内部实现的时候对其进行控制,然后使用我们代码的开发者就只需要无脑的调用就可以了,能够促进API设计的更加友好。

1、基本概念

单例模式,保证一个类仅有一个实例,并提供一个访问它的全局节点。

通常我们可以让一个全局变量使得一个对象被访问,但它不能被防止你实例化多个对象,一个最好的方式就是,让类自身负责保存它的唯一实例。

这个类可以保证没有其它实例可以被创建。

它的UML图如下:

从上图中可以看出,将其构造方法私有化 ,这样外界就无法实例化它了,并且暴露出了一个访问它唯一实例的方法

2、代码示例

ts 复制代码
class Singleton {
  /**
   * 内部持有全局唯一的实例
   */
  private instance: Singleton | null = null;
  /**
   * 私有化构造函数
   */
  private constructor() {}
  /**
   * 暴露访问其唯一实例的访问方法
   * @returns
   */
  getInstance(): Singleton {
    return this.instance || (this.instance = new Singleton());
  }
}

这个代码范式仅仅是使用TS根据上述的UML图实现的,而实际上,JavaScript比较灵活,因此前端在实现单例模式的时候,往往可以很简单,不比拘泥于上述的UML图。

3、前端开发中的实践

3.1 单例的Notice组件

一个大家比较熟悉的场景就是前端的Notice组件了,比如Element UIMessage组件,如果频繁的执行(用户点击的过快的话), 就会出现以下这种场景:

我个人觉得这种交互是比较糟糕的,但是Element UI的设计团队为了把最大的灵活度交给开发者,它并没有在实现的时候就保证其单例,因此,我们可以使用单例模式对Message组件进行封装。

因此,我们需要使用单例模式对Message组件进行封装:

js 复制代码
import Vue from "vue";
/**
 * 单例的Message组件
 */
class SingletonMessage {
  static instance = null;

  constructor() {
    // 不允许当前类实例化
    throw new Error("this class can not called by new");
  }

  static show(options) {
    // 如果当前实例存在则什么事儿都可以不做了
    if (this.instance) {
      return;
    }
    let config;
    if (typeof options === "string") {
      config = {
        message: options,
        onClose: () => {
          // 做一些清理工作
          this.instance = null;
        },
      };
    } else {
      const { onClose, ...others } = options;
      config = {
        ...others,
        onClose: (...args) => {
          // 处理额外的清理工作
          this.instance = null;
          // 处理默认的参数
          typeof onClose === "function" && onClose.apply(this, args);
        },
      };
    }
    this.instance = Message(config);
  }

  static close() {
    if (!this.instance) {
      return;
    }
    this.instance.close();
  }
}

Vue.prototype.$singletonMessage = SingletonMessage;

看得仔细的同学可能会觉得上面的代码跟单例模式的UML的表示还是有一些差别的,切记学设计模式不要死板(很多时候,我们都在借鉴其设计思想),使用设计模式最大的动机在于将我们的代码写的易于维护,如果应用了设计模式反而使得我们的代码维护成本更高了,那就应该反思是不是做错了。

此例受制于Element UI的限制,Message组件每次关闭的时候都会移除DOM,所以看起来好像并不是那么"纯",因此仅借鉴了单例模式的思想,达到了业务预期。

除此之外,还有些对象也是全局单例的,可能你每天都在用到,但你并没有在意,它就是->Math对象,LocalStorage对象,SessionStorage对象。

3.2 封装Bridge

我们团队的运营活动是运行在App中的webview中的,因此我们需要和原生的代码进行通信,于是客户端就像H5的环境中注入了一个bridge对象。

如果运营活动的开发者直接操作这个bridge对象的话,它需要去关注很多业务逻辑,比如bridge怎么初始化,是否初始化成功,在没有初始化成功的时候怎么降级处理,这些如果都让业务开发者自行处理的话,开发效率就太低了。

于是,我们就可以用一个统一的类来封装整体的逻辑,并且这个类不需要多次初始化。

ts 复制代码
// bridge对象的封装,因都是一些业务代码,我就不向大家展示了
class JsBridge {}

export class GlobalBridge {
  static instance: GlobalBridge

  static getInstance() {
    if (!this.instance) {
      this.instance = new JsBridge()
    }
    return this.instance
  }
}

除此之外,在很多团队统一封装axios也是可以采用单例模式,就可以参考这种方式。

总结

在文章的开头就向大家阐述了,单例模式主要的应用场景,单例模式是一种很简单的设计模式,也是每一个前端开发者必须要掌握的设计模式。

在实际的开发中,我们可以采用惰性单例设计。所谓惰性单例就是说,并不是系统一运行起来就立即去创建单例的类,而是当外部调用者需要的时候再创建,这将在某种程度上优化系统的初始化时间。

如果大家喜欢我的文章,可以多多点赞收藏加关注,你们的认可是我最好的更新动力,😁。

相关推荐
ziyue75757 分钟前
vue修改element-ui的默认的class
前端·vue.js·ui
树叶会结冰28 分钟前
HTML语义化:当网页会说话
前端·html
冰万森34 分钟前
解决 React 项目初始化(npx create-react-app)速度慢的 7 个实用方案
前端·react.js·前端框架
牧羊人_myr1 小时前
Ajax 技术详解
前端
浩男孩1 小时前
🍀封装个 Button 组件,使用 vitest 来测试一下
前端
蓝银草同学1 小时前
阿里 Iconfont 项目丢失?手把手教你将已引用的 SVG 图标下载到本地
前端·icon
李广坤1 小时前
状态模式(State Pattern)
设计模式
布列瑟农的星空1 小时前
重学React —— React事件机制 vs 浏览器事件机制
前端
一小池勺2 小时前
CommonJS
前端·面试