【JS设计模式】单例模式

107 阅读3分钟

定义

保证一个类仅有一个实例,并提供一个访问它的全局访问点。 单例模式应该是比较常见的一种设计模式了,比如浏览器中window对象。

实现单例模式

实现单例模式需要一个变量标志这个类是否已经创建过对象,如果是,就直接返回这个创建过的对象,如果不是用这个类新创建一个对象发挥出去。

var Singleton = function(name){
    this.name = name
}
Singleton.instance =null
Singleton.prototype.getName = function(){
    console.log(this.name)
}
Singleton.getInstance = function(){
    if(!this.instance){
        this.instance = new Singleton(name)
    }
    return this.instance
}
var a = Singleton.getInstance('name1')
var b = Singleton.getInstance('name2')
console.log(a===b) // true

这个例子有个问题,就是需要使用者知道Singleton是一个单例类,还必须使用Singleton.getInstance来获取对象,增加了不透明性。

透明的单例模式

下面的例子实现了一个“透明”的单例模式。

var CreateDiv(function(){
    var instance
    var CreateDiv = function(html){
        if(instance){
            return instance
        }
        this.html = html
        this.init()
        return instance = this
    }
    CreateDiv.prototype.init = function(){
        var div = document.createElent('div')
        div.innerHTML = this.html
        document.body.appendChild(div)
    }
    return CreateDiv
})()

var a = new CreateDiv('name1')
var b = new CreateDiv('name2')
console.log(a===b) // true

虽然实现了“透明”的单例模式,但是有两个缺陷: 1.自执行的匿名函数和闭包,不利于阅读;2.CreateDiv这个方法其实做了两件事情,一是保证只有一个对象,而是创建对象,没有遵循“单一职责原则”,不是特别好的做法。假如我们并不需要只创建唯一对象,而是想创建很多对象,那就需要修改CreateDiv方法了。

用代理实现单例模式

现在用代理类的方式,解决上面的问题。CreateDiv就用来创建div,保证唯一对象用代理类实现。

 var CreateDiv = function(html){
    this.html = html
    this.init()
}
 CreateDiv.prototype.init = function(){
    var div = document.createElent('div')
    div.innerHTML = this.html
    document.body.appendChild(div)
}
//引入代理类
var ProxySingletonCreateDiv = (function(){
    var instance
    return functoin(html){
        if(!instance){
            instance = new CreateDiv(html)
        }
        return instance
    }
})()

var a = new ProxySingletonCreateDiv('name1')
var b = new ProxySingletonCreateDiv('name2')
console.log(a===b) // true 

JavaScript中的单例模式

我们知道JS中没有类的概念,是ES6才出现类的概念,单例模式的核心是确保只有一个实例, 并提供全局访问。全局变量不是单例模式,但在JS开发中,会把全局变量单例模式来使用。

但是全局变量存在很多问题,造成命名空间的污染,一不小心就会命名冲突,所以在JS中有以下几种方式,降低命名空间污染的问题。

  1. 使用命名空间
  2. 使用闭包封装私有变量

但这不是最好的解决办法,后面就有了模块化的概念,只要不在一个模块中命名冲突即可,完美的解决了全局命名冲突的问题。

惰性单例

惰性单例是指在需要的时候再创建对象,这样可以提高一些性能,比如我们在调用 Singleton.getInstance再创建对象,而不是在页面加载好后就立即创建。

通用的惰性单例:

var getSingle = function( fn ){  
    var result;  
    return function(){  
        return result || ( result = fn .apply(this, arguments ) );  
    }  
};

然后在登录场景中使用举例:

var createLoginLayer = function(){  
    var div = document.createElement( 'div' );  
    div.innerHTML = '我是登录浮窗';  
    div.style.display = 'none';  
    document.body.appendChild( div );  
    return div;  
};  
  
var createSingleLoginLayer = getSingle( createLoginLayer );  
  
document.getElementById( 'loginBtn' ).onclick = function(){  
    var loginLayer = createSingleLoginLayer();  
    loginLayer.style.display = 'block';  
};