单例模式 - js设计模式系列

164 阅读3分钟

设计模式的定义是:在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案。对于js来说,设计模式就是一种将属性和方法组织在一起的一种方式

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

单例模式的应用:

  • 线程池
  • 全局缓存
  • 浏览器中的window对象

单例模式的实现可以通过增加一个标志来实现: 给Singleton增加一个instance属性,初始时置为null,对Singleton实例化后,让instance指向实例化后的对象,通过返回instance,就可以保证并且获得唯一的实例。

添加函数属性实现单例模式

function Singleton(name){
    this.name = name;
    this.instance = null;
}
Singleton.getInstance = function (name){
    if(!this.instance){
        this.instance = new Singleton(name)
    }
    return this.instance;
}

Singleton.prototype.getName = function(){
    return this.name;
}

// 测试
let a = Singleton.getInstance('John')
let b = Singleton.getInstance('Lili');
console.log(a);
console.log(b)
console.log(a === b)

但是这种方式实现的话,会给Singleton增加一个instance属性,如果不需要这个属性的话,可以将这个属性利用自执行的匿名函数和闭包,放在Singleton.getInstance的作用域中。

function Singleton(name){
    this.name = name;
}
// 立即执行的函数,返回一个闭包函数
Singleton.getInstance = (function (){
    let instance = null;
    return function (name){
        if(!instance){
            instance = new Singleton(name)
        }
        return instance;
    }
})();

Singleton.prototype.getName = function(){
    return this.name;
}

// 测试
let a = Singleton.getInstance('John')
let b = Singleton.getInstance('Lili');
console.log(a);
console.log(b)
console.log(a === b)

以上两种实现方式相对简单,但是它与我们平时用new实例化方式不同,增加了这个类的“不透明性”。 那么接下来就通过使用new的方式来实现一下,其实也是利用立即执行的匿名函数和闭包将Singleton函数进行包装。

需要对是否采用new的形式进行判断,因为如果不是采用new的形式,那么this会指向window或者undefined,程序会报错。

new形式的单例模式实现

const Singleton = (function(){
    // 标志
    let instance = null;
    // 返回的匿名函数
    return function(name){
        if(!instance){
            // 判断是否是通过new的方式
            if(new.target !== undefined){
                this.name = name;
                instance = this;
            }
            else{
                // 不是new的方式则报错
                throw new Error('must use new')
            }
        }
        // 返回实例
        return instance;
    }
})();

// 测试
let a = new Singleton('John')
let b = new Singleton('Lili');
console.log(a);
console.log(b)
console.log(a === b)

上面这种实现方式,其实是将类的构造功能和单例功能耦合在一起,增加了程序的复杂度,也不利于理解。因此可以将这两个功能分开。引入代理类来实现单例的功能。

function Singleton(name){
    this.name = name;
}
const ProxySingleton = (function(){
    // 标志
    let instance = null;
    // 返回的匿名函数
    return function(name){
        if(!instance){
            // 判断是否是通过new的方式
            if(new.target !== undefined){
                instance = new Singleton(name);
            }
            else{
                // 不是new的方式则报错
                throw new Error('must use new')
            }
        }
        // 返回实例
        return instance;
    }
})();

// 测试
let a = new ProxySingleton('John')
let b = new ProxySingleton('Lili');
console.log(a);
console.log(b)
console.log(a === b)

可以对比一下,Singleton跟一开始一样,而ProxySingleton则与上面的实现方式基本一致,只是将this.name = name; instance = this; 替换成了instance = new Singleton(name);,但是职责划分的就很清晰了。而且ProxySingleton还可以通过传入函数的方式,作为一个通用的单例功能函数。