一文搞懂设计模式中的单例模式

349 阅读4分钟

前言

设计模式是一种用来解决常见问题的模板。单例模式就是其中的一种,它确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。本文将会通过两个示例,来帮助你理解单例模式的概念及其在JavaScript中的实现。

正文

什么是单例模式?

举个例子,假设你在开发一款游戏,游戏中有一个分数管理器,用来记录玩家的分数。这个分数管理器应该是唯一的,因为每个玩家只能有一个分数,而这个分数需要在多个地方被访问和修改。此时,如果我们每次需要分数管理器的时候都去创建一个新的对象,那么就会产生多个分数管理器实例,这显然是不符合逻辑的。因此,我们需要确保分数管理器在整个游戏中只有一个实例,这就是单例模式的基本思想。

实现单例模式

小提一嘴

我们先来看这个例子,回顾一下new实例的过程:

function Person() {
  // let obj = {   // 1
  //   name: 'Tom' 
  // }
  // Person.call(obj) // 2
  // obj.__proto__ = Person.prototype  // 4
  // return obj  // 5

  this.name = 'Tom'  // 3
}

let p1 = new Person()
let p2 = new Person()

console.log(p1 === p2);  // false

每个创建的实例地址都不一样,不可能有两个相同的对象。

function Person() {
  this.name = 'Tom'
}
Person.insance = 'hi'
Person.prototype.insance2 = 'hello'

let p1 = new Person()
let p2 = new Person()
console.log(p1.insance)    // undefined
console.log(p1.insance2)   // hello

// p1.__proto__ === Person.prototype

我们往构造函数加属性没用的,要往构造函数的原型上加new的实例才会继承到,对象的隐式原型=构造函数的显示原型。


好废话说完,进入正题,下面我们将通过两个示例来实现单例模式,第一个例子使用静态方法,第二个例子使用闭包

示例1:使用静态方法实现单例模式

在这个示例中,我们将创建一个名为Person的类,并在这个类中定义一个静态方法getInstance()。这个方法会检查是否已经创建了Person的实例,如果没有,则创建一个新的实例;如果有,则直接返回该实例。

class Person {
  constructor() {
    this.name = 'Tom'
  }
  static getInstance() {
    if (!Person.instance) {
      Person.instance = new Person()
    }
    return Person.instance
  }
}

let p1 = Person.getInstance()
let p2 = Person.getInstance()

console.log(p1 === p2);  // true

在这个例子中,我们通过Person.getInstance()来获取Person类的唯一实例。当我们首次调用getInstance()时,会创建一个新的Person对象,并将其存储在Person.instance中。随后再次调用getInstance()时,由于Person.instance已经存在,因此不会创建新的对象,而是直接返回已有的实例。

示例2:使用闭包实现单例模式

另一个实现单例模式的方式是利用JavaScript的闭包特性。我们知道闭包的作用是不是可以实现变量私有化,那它就可以让我们的实例一直是闭包里面的那个实例。如果不知道闭包可以看我的这篇文章<你不知道的JavaScript>,里面有介绍到this闭包调用栈等知识点

class Person {
  constructor() {
    this.name = 'Tom'
  }
  static getInstance() {
    let instance = null
    return function() {
      if (!instance) {
        instance = new Person()
      }
      return instance
    }
  }
}

const simple = Person.getInstance()
let p1 = simple()
let p2 = simple()

console.log(p1 === p2);  // true 

在这个例子中,getInstance()返回的是一个函数function,当函数getInstance()调用完就会出栈,因为里面的函数function用到了 instance变量,所有就会留一个小背包,里面装有所需要的 instance,这个就是所谓的闭包, getInstance()返回的函数function赋给了simple,然后调用,如果为空就会创建一个新的Person对象,闭包里的instance就为这个实例了,那么后的instance一直不为空因此不会创建新的对象,而是直接返回闭包里的实例。

单例模式的优点与缺点

  • 优点

    • 保证系统内存中该类只存在一个实例,节省内存空间;
    • 控制对全局变量的访问,防止其被滥用;
    • 提供一个全局访问点,方便访问。
  • 缺点

    • 单例模式的类由于没有接口,所以无法继承,扩展困难;
    • 违反了开闭原则,一旦单例类的功能发生变化,就需要修改代码;

总结

单例模式是一种简单而有效的设计模式,它有助于管理和优化应用程序中的资源使用。通过本文的介绍,你应该能够理解单例模式的概念以及如何在JavaScript中实现它。本文到此就结束了,希望对你有所帮助,感谢你的阅读!

image.png