前端小白变形记:你要学会这些设计模式!第二弹:单例模式

441 阅读5分钟

前言

  1. 从这一篇开始,我会持续地更新每一种设计模式的内容,争取用通俗易懂的语言讲解和解释清楚。如果对你有帮助,请不要吝啬手中的赞~ 如果对文章内容有任何疑惑都可以在评论区提出和讨论~

  2. 本系列文章中的完整源码已上传 github 仓库,你可以在这里 FatMii/Design-Mode 获取。同样的,如果对你有帮助,请给我一个star~谢谢

  3. 设计模式合集链接:

    前端小白变形记:你要学会这些设计模式!首发:工厂模式

    前端小白变形记:你要学会这些设计模式!第二弹:单例模式

    前端小白变形记:你要学会这些设计模式!第三弹:责任链模式

    前端小白变形记:你要学会这些设计模式!第四弹:观察者模式

    前端小白变形记:你要学会这些设计模式!第五弹:发布订阅模式

Hello~大家好,上一篇内容讲完了工厂模式,这一篇我们继续学习单例模式

什么是单例模式呢?顾名思义,就是一个类只能生成一个实例对象,并提供一个全局访问点来获取这个实例。这个模式在很多情况下都非常有用,特别是当你需要控制某些共享资源或服务的访问时,如状态管理,配置对象等。

除此之外,单例模式被广泛应用于多种框架和工具包场景,对于 Vue.js 全家桶,即 Vue.js 框架及其官方维护的周边库(如 Vuex、Vue Router等),单例模式的应用尤为明显。

前端开发中单例模式的应用场景:

  1. 状态管理(Vuex)

    • Vuex:Vuex 是 Vue 的官方状态管理库,它实现了一个集中式存储,用来存储所有组件的共享状态。这个存储是响应式的,当 Vue 组件从 store 中读取状态的时候,若状态发生变化,相应的组件也会得到高效的更新。Vuex 的 store 是一个单例,整个应用只创建一个 store 实例,确保了应用各个部分状态的一致性。
  2. 路由管理(Vue Router)

    • Vue Router:Vue Router 是 Vue 的官方路由管理器,它允许你建立单页面应用中的路由系统。在 Vue Router 中,整个应用只有一个 router 实例,这个实例在不同组件之间共享,保证了路由配置的统一性和路由状态的一致性。每次路由变化,对应的视图和组件状态会更新,这需要确保全局只有一个路由实例来控制这些更新。

单例模式的核心特点:

  • 单一实例:确保只创建一个类的实例。
  • 全局访问点:提供一个全局可访问的接口用于访问该实例。
  • 自我管理:单例类通常自己负责管理它的创建和生命周期

单例模式的缺点:

  1. 全局状态:单例模式本质上提供了一个全局状态,这可能导致代码难以测试和维护,因为系统的不同部分可能会以不可预见的方式相互影响。
  2. 违反单一职责原则:由于单例类除了自己的方法外,还负责管理自身的唯一实例,这可能会使单例类过于复杂。
  3. 扩展困难:由于单例模式的特性,扩展单例类或其子类通常比较复杂,有时可能需要修改单例实现,这可能会导致代码违背开放-封闭原则。

代码实现

class Singleton {
  static instance;

  static getInstance() {
    if (!Singleton.instance) {
      Singleton.instance = new Singleton();
    }
    return Singleton.instance;
  }
  
  constructor() {
    if (Singleton.instance) {
      return Singleton.instance;
    }
    Singleton.instance = this;
  }

  // 示例方法
  someMethod() {
    console.log("I am doing something.");
  }
}

// 使用单例
const instance1 = new Singleton();
const instance2 = new Singleton();

instance1.someMethod(); // Output: I am doing something.

console.log(instance1 === instance2); // Output: true


在ES6中,通过使用class关键字和static静态属性,我们可以非常方便地实现单例模式。使用static关键字声明的属性或方法,如示例中的instance属性和getInstance方法,将成为类的静态属性静态方法

那么,静态属性、静态方法普通属性、普通方法有何区别呢?

关键区别在于:静态属性和方法属于类本身,而普通属性和方法则属于由类构造的实例对象。

简而言之,当你在类上定义静态成员时,这些成员可直接通过类访问,而不依赖于任何实例。这使得静态成员非常适合用作实现单例模式的工具,因为它们不会随着实例的创建而重复生成。


console.log(Singleton.instance);      // Singleton {}
console.log(Singleton.getInstance())  // Singleton {}

console.log(instance1.instance);   // undefined,因为实例对象instance1上没有声明过普通属性instance
console.log(instance1.getInstance()); // ReferenceError: instance1 is not defined 
                                      // 静态方法调用直接在类上进行,不能在类的实例上调用。静态方法通  常用于创建实用程序函数。

关于static的更详细介绍,你可以在CDN上学习:static

ES5实现单例模式

如果没有es6中的static关键字我们想实现一个单例模式是比较繁琐的,需要借助到闭包

在JavaScript中使用闭包来实现单例模式主要是为了提供几个关键的好处,包括封装性数据隐私状态持久化。闭包允许某些数据保持私有,而对外只暴露必要的接口,这是实现单例模式的理想选择。

var Singleton = (function() {
    // 私有变量,存储单例实例
    var instance;

    // 构造函数定义
    function Singleton() {
        if (instance) {
            return instance;
        }

        instance = this;

        // 定义一些属性和方法
        this.property = "I am a property";
        this.method = function() {
            console.log("I am a method");
        };
    }

    return Singleton;
})();

// 创建实例
var instance1 = new Singleton();
var instance2 = new Singleton();

console.log(instance1 === instance2); // 输出: true

instance1.method(); // 输出: "I am a method"


在这个实现中,Singleton构造函数内部检查是否已经存在一个实例。如果存在,它将直接返回这个实例;如果不存在,它将创建一个新的实例。这是通过在构造函数内部检查一个私有变量instance来实现的,该变量在构造函数外部通过闭包保持私有。这种方法有效地确保了类的单一实例