JavaScript设计模式:单例模式

235 阅读3分钟

单例模式(Singleton Pattern)是一种创建型设计模式,它确保一个类只有一个实例,并提供了一个全局访问点来访问该实例。它是设计模式中相对容易理解、上手的一种模式。他有以下几个特点:

  • 单例类只能有一个实例。
  • 单例类必须自己创建自己的唯一实例。
  • 单例类必须给所有其他对象提供这一实例。

单例模式的实现

下面是基于ES6“类”的单例模式的实现:

class Singleton {
    static instance = new Singleton()

    static getInstance() {
        return Singleton.instance
    }

    hello() {
        console.log('hello')
    }
}

const s1 = Singleton.getInstance()
const s2 = Singleton.getInstance()

s1 === s2 // true

从以上代码中可以看出在创建Singleton类时通过静态属性instance保存其实例,该类通过getInstance方法来获取其实例,返回instance静态属性来保证该单例类只能有一个实例。

还可以使用ES5来实现:

function Singleton() {}

Singleton.prototype.hello = function () {
    console.log('hello')
}

Singleton.getInstance = (function () {
    var instance = new Singleton()
    return function () {
        return instance
    }
})()

惰性单例

惰性单例指的是在需要的时候才创建实例对象。在之前单例模式的实现中,类的实例在开始时就已经创建。我们简单修改一下上面的代码就可以实现:

function Singleton() {}

Singleton.prototype.hello = function () {
    console.log('hello')
}

Singleton.getInstance = (function () {
    var instance = null
    return function () {
        if (!instance) {
            instance = new Singleton()
        }
        return instance
    }
})()

在修改后,instance实例对象总是在调用Singleton.getInstance的时候才被创建。

优化单例模式的实现

在之前的实现中,是通过调用Singleton.getInstance来获取实例。在大多数的情况下,我们都是通过new操作符来获取实例对象,这种方式也更符合于编码习惯。

var Broadcast = (function () {
    var instance

    var Broadcast = function (name) {
        if (instance) {
            return instance
        }
        this.name = name
        return instance = this
    }

    Broadcast.prototype.broadcasting = function (content) {
        console.log(`${this.name}广播站:${content}`);
    }

    return Broadcast
})()

var a = new Broadcast('阳光小区a')
var b = new Broadcast('阳光小区b')

a === b // true

至此,我们可以通过new操作符的方式来创建和获取实例对象了。心细如你,大概也发现了这里的代码还是有些问题,Broadcast构造函数中混杂了业务逻辑和保证实例对象唯一的逻辑,假如某一天业务的变化,要让这个单例类变成一个普通的可以产生多个实例对象的类,那就需要改写Broadcast构造函数了。

如果了解或接触过“单一职责原则”,就可以明确这是一种不好的写法,至少这个构造函数看起来很奇怪。

我们通过引入代理类的方式来解决这个问题。

var Broadcast = function (name) {
    this.name = name
}

Broadcast.prototype.broadcasting = function (content) {
    console.log(`${this.name}广播站:${content}`);
}

// 引入代理类,将保证实例对象唯一的逻辑移到了代理类中
var proxySingletonBroadcast = (function () {
    var instance

    return function (name) {
        if (!instance) {
            instance = new Broadcast(name)
        }
        return instance
    }
})()

跟以前不一样的是,现在我们将保证实例对象唯一的逻辑移到了代理类proxySingletonBroadcast中。这样Broadcast变成了一个普通的类,与代理类proxySingletonBroadcast相结合可以达到单例模式的效果。

实际运用案例

我们简单举几个例子,来看看单例模式在前端领域中的实际运用:

  • Redux 中的 Store ,Store 是一个单例对象,用于管理应用的状态。
  • 模态对话框管理器,在前端应用中,经常需要使用模态对话框来与用户进行交互,用单例模式来设计一个模态框管理器来保证模态框实例的唯一。
  • 在Vue应用中,通常会创建一个根Vue实例,它是整个应用的入口点。该实例负责管理应用的状态、渲染根组件以及处理全局事件等,因此在应用中只会有一个根实例。

以上便是对JavaScript中单例模式的介绍,本文完。