JavaScript设计模式与开发实践系列之单例模式

1031 查看

本系列为《JavaScript设计模式与开发实践》(作者:曾探)学习总结,如想深入了解,请支持作者原版

单例模式

实现单例模式

单例模式的定义是:保证一个类仅有一个实例,并提供一个访问它的全局访问点
单例模式是一种常用的模式,有一些对象我们往往只需要一个,比如登录窗口,这个窗口是唯一的,无论我们点击多少次登录按钮,这个窗口只会被创建一次,那么这个窗口就适合用单例模式来创建。

要实现一个标准的单例模式并不复杂,无非是用一个变量来标志当前是否已经为某个类创建过对象,如果是,则在下一次获取该类的实例时,直接返回之前创建的对象。代码如下:

var Singleton=function(name){
    this.name=name;
    this.instance=null;
};
Singleton.prototype.getName=function(){
    alert(this.name);
};
Singleton.getInstance=function(){
    if(!this.instance){
        this.instance=new Singleton(name);
    }
    return this.instance;
};
var a=Singleton.getInstance('sven1');
var b=Singleton.getInstance('sven2');

alert(a===b);//true

或者

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;
    }
})();

我们通过Singleton.getInstance来获取Singleton类的唯一对象,这种方式相对简单,但是不透明。跟以往通过new XXX的方式获取对象不同,这里偏要使用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.createElement('div');
        div.innerHtml = this.html;
        document.body.appendChild(div);
    };
    return CreateDiv;
})()

var a = new CreateDiv('sven1');
var b = new CreateDiv('sven2');
alert(a===b);//true

虽然现在完成了一个透明的单例类的编写,但它同样有一些缺点。为了把instance封装起来,我们使用了自执行的匿名函数和闭包,并且让这个匿名函数返回真正的Singleton构造方法,这增加了一些程序的复杂度,阅读起来也不是很舒服。
假设我们某天需要让这个单例类变成一个普通的类,即可以产生多个实例,那我们必须改写CreateDiv构造函数,这种修改会带来很多不必要的麻烦。

用代理实现单例模式

我们首先创建一个普通的CreateDiv类:

var CreateDiv = function(html) {
  this.html = html;
  this.init();
};
CreateDiv.prototype.init = function() {
  var div = document.createElement('div');
  div.innerHtml = this.html;
  document.body.appendChild(div);
};

接下来引入代理类ProxySingletonCreate:

var ProxySingletonCreate = (function() {
  var instance;
  return function(html) {
    if (!instance) {
      instance = new CreateDiv(html);
    }
    return instance;
  }
})();

var a = new ProxySingletonCreate('sven1');
var b = new ProxySingletonCreate('sven2');
console.log(a === b);

这样一来,CreateDivProxySingletonCreate组合起来,实现了单例模式的效果。

JavaScript中的单例模式

前面提到的几种单例模式的实现,更多的是接近传统面向对象语言中的实现,单例对象从中创建而来。但JavaScript其实是一门无类的语言,所以生搬单例模式的概念并无意义。
单例模式的核心是:确保只有一个实例,并提供全局访问
全局变量不是单例模式,但在实际应用中,我们经常会把全局变量当成单例来使用。
例如:

var a = {};

全局变量可以满足上述的两个条件,但却存在许多问题:它很容易造成命名空间污染。作为普通的开发者,我们有必要减少全局变量的使用,一下几种方式可以相对降低全局变量带来的命名污染。

使用命名空间

最简单的方法依然是使用对象字面量方式:

var namespace1={
    a:function(){
    },
    b:function(){
    }
};

我们还可以动态的创建命名空间:

var myApp = {};
myApp.namespace = function(name) {
  var parts = name.split('.');
  var current = myApp;
  for (var i in parts) {
    if (!current[parts[i]]) {
      current[parts[i]] = {};
    }
    current = current[parts[i]];
  }
};

myApp.namespace('event');
myApp.namespace('dom.style');

console.log(myApp);

使用闭包封装私有变量

var user=(function(){
    var _name='sven',_age=29;
    return {
        getUserInfo:function(){
            return _name+'_'+_age;
        }
    }
})();

惰性单例

惰性单例指的是在需要的时候才创建对象实例惰性单例是单例模式的重点,这种技术在实际开发中非常有用
我们先抽取出一个管理单例的逻辑对象:

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

创建对象的方法fn被当做参数传入getSingle。

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.width = '100px';
  div.style.height = '100px';
  div.style.background = 'red';
  document.body.appendChild(div);
  return div;
};

aa = getSingle(createLoginLayer);
aa();

result变量因为身在闭包中,永远不会被销毁。在将来的请求中,如果result已经被赋值,那么他将返回这个值。
以下是演示地址:

惰性单例演示地址
其他演示地址