JS设计模式——单例模式剖析

转载于原文地址:https://blog.csdn.net/q1056843325/article/details/52933426

举一个通俗的例子,在页面中点击登录按钮,弹出了一个登录浮窗,这个登录浮窗是唯一的,无论我们单击多少次,浮窗只会创建一次。

其实我们可能无意中都会使用过单例模式,我们的做法往往都是使用一个变量来标志当前是否已经为某个类创建了对象, 如果true,那么下一次再想获得这个类的实例时,直接返回之前创建过的对象。

单例模式的核心是确保只有一个实例,并提供全局访问。

其实在JavaScript中,单例模式并没有这么复杂 

var a = {};

我们这样创建了对象a,它确实独一无二 
而且满足了单例模式的两个条件

  • 一个实例
  • 全局访问

但是全局变量很容易造成命名空间污染 ,如果项目很大的话,不小心覆盖了变量那就是致命的。

所以,在详细讲解这个单例模式之前,我们先来讨论这样一个问题,怎样降低全局污染? (全局污染也就是变量大量存在于全局作用域污染了全局空间 )

降低全局污染有两种办法:

1、使用命名空间

var namespace_payen = {
    a: function(){
        //...
    }
    b: function(){
        //...
    }
}

适当使用命名空间,并不会杜绝全局变量,但是可以减少全局变量的数量

2、使用闭包封装私有变量

var payen = (function(){
    var _name = 'payson.Tsung',
        _age = 19;
    return {
        getInfo: function(){
            return _name + ' ' + _age;
        }
    }
})();

变量被封装在了闭包内,只暴露一些接口用于外部通信,从而避免了对全局的命令污染

下面我来谈谈这个单例模式 

先来个简单的例子 

下面我声明了一个函数,每次调用都创建一个小方块

function createDiv(){
    var div = document.createElement('div');
    div.style.width = '100px';
    div.style.height = '100px';
    div.style.background = 'red';
    div.style.marginBottom = '10px';
    document.body.appendChild(div);
}
createDiv();
createDiv();
createDiv();

调用了三次,页面出现了三个小方块 

下面我就使用单例模式,让它只创建一个div 

var createDiv = (function(){
    var div;
    return function(){
        if(!div){
            div = document.createElement('div');
            div.style.width = '100px';
            div.style.height = '100px';
            div.style.background = 'red';
            div.style.marginBottom = '10px';
            document.body.appendChild(div);
        }
    }
})();
createDiv();
createDiv();
createDiv();

 再来看看页面,只有一个小方块 

div声明在立即执行函数中作为私有变量 ,没有执行函数前, div值为undefined ,第一次执行函数时,判断div,如果没有,创建了一个DOM节点并且插入到了文档; 随后再执行函数时,div变量已经缓存了刚刚创建的DOM节点,不再创建 ,无论执行几次,小方块只会创建一次 ,这就是单例模式,而且是一个惰性单例。

惰性单例就是在需要的时候才创建对象实例,而非在页面加载时就创建 ,这样做的好处大家都知道。

虽然我们完成了惰性单例,但是我们同样发现了问题

  • 违反了单一职责原则,创建对象和管理单例放在了一个函数中createDiv
  • 如果我们还想创建一个其他的唯一对象,那就只能copy了

综上,我们需要把不变的部分隔离出来,把可变的封装起来,这给予了我们扩展程序的能力,符合“开放-封闭原则”;

下面我们就抽出管理单例的逻辑 ,无论怎样抽取,万变不离其中,用一个变量来标志是否创建过对象

var getSingle = function(fn){
    var result;
    return function(){
        return result || (result = fn.apply(this, arguments));
    }
};
var createDiv = function(){
    var div = document.createElement('div');
    div.style.width = '100px';
    div.style.height = '100px';
    div.style.background = 'red';
    div.style.marginBottom = '10px';
    document.body.appendChild(div);
    return div;
};
var createSingleDiv = getSingle(createDiv);
createSingleDiv();
createSingleDiv();
createSingleDiv();

创建的DOM节点保存在了result中 ,result变量因为自身在闭包中,不会被销毁,如果result已经被赋值了,那么它将返回这个值 ;

单例模式很简单,而且也十分实用,他不仅仅用于创建对象,还有很多其他用途 ,比如说只绑定一次事件啦之类的