Fork me on GitHub

单例模式


用一个变量来标志当前是否已经为某个类创建过对象,如果是,则在下一次获取该类的实例时,直接返回之前创建的对象。

实现单例模式

//1.实现单例模式
var Singleton = function (name) {
  this.name = name;
};
Singleton.prototype.getName = function() {
  alert(this.name);
};
Singleton.getInstance = (function() {
  var instance = null;
  return function(name) {
    if (!instance) {
      instance = new Singleton(name);
    }
    return instance;
  }
})();
var a = Singleton.getInstance('sven1');
var b = Singleton.getInstance('sven2');

console.log(a === b); //true

以上,我们通过Singleton.getInstance来获取Singleton类的唯一对象,虽然简单但是增加了这个类的“不透明性”,Singleton类的使用者必须知道这是一个单例类,跟以往通过new xxx的方法不同

透明的单例模式

有了以上的不足,我们现在就来解决

//透明的单例模式 
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.createElement('div');
    div.innerHTML = this.html;
    document.body.appendChild(div);
  };

  return CreateDiv;
})();
var aa = new CreateDiv('sven1');
var bb = new CreateDiv('sven2');

console.log(aa === bb);

以上代码,CreateDiv做了创建对象和执行初始化init方法,且保证只有一个对象。
但是,假如有一天我们需要利用这个类,在页面中创建千千万万个div,即要让这个类从单例类变成一个普通的可以产生多个实例的类,那我们必须改写CreateDiv构造函数,把控制创建唯一的那一段去掉…这样好像有点太麻烦了

用代理实现单例模式

这里就可以引入代理类的方式,思想就是将负责管理的单例逻辑移到一个代理类中,这样CreateDiv就变成一个普通的类,代码此处省略…

JavaScript中的单例模式

前面几种单例模式的实现,更多是接近传统面向对象语言中的实现,单例从“类”中创建出来。
然而,js作为一个没有类的语言,生搬单例模式的概念并无意义。我们只要一个对象,为什么要创建一个类呢?我们只是要一个唯一的实例就好了..因此我们可以直接用var a = {}来创建对象字面量。

惰性单例

惰性单例:需要的时候才创建。
例如,以一个登录悬浮窗为例,我们可以在页面加载的时候创建好这个div窗口,然后点击登录按钮的时候开始显示,但是这样如果用户只是随便浏览下就白白浪费了一些dom节点。
还有一种方法,点击的时候再创建,关闭的时候销毁。但是如果用户频繁的进行注销等操作,我们就要频繁的创建和删除div,显然这也不合理。
所以,这时候惰性单例模式就出马了。

var createLoginLayer = (function() {
    var div;
    return function() {
        if (!div) {
            div = document.createElement('div');
            //创建添加等dom操作...
        }
        return div;
    }
})();

$("xxx").onclick = function() {
    var loginLayer = createLoginLayer();
    loginLayer.show();
}

通用的惰性实例

看起来好像没什么问题,然而,他还是违反了单一的原则,创建的对象逻辑都放在createLoginLayer内部,如果我们要创建别的东西例如iframe,就要几乎将这个函数照抄一遍了,因此我们应该把不变的部分隔离出来。

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

var createLoginLayer = function() {
    //创建登录框
}  

var createSingleLoginLayer = getSingle(createLoginLayer);

于是,目的达成。
当然,单例模式用途远远不止于创建对象,比如我们在渲染完页面的时候,接下来要给这个列表绑定click事件,如果是ajax动态向列表里面加载数据,实际上只需要第一次渲染的时候绑定。如果不用jq的$("").one('click', function(){})的话,我们也可以用单例模式,做法类似。

我知道是不会有人点的,但万一有人想不开呢?