单例模式:一个人的寂寞

前言

单例模式(Singleton)说白了就是一山不能容二虎。只允许实例化一个实例对象。

原理

那单例模式的原理是什么呢?它的套路也很简单,在创建对象之前先判断下实例对象是否已经存在,如果实例对象不存在,那么就创建它,否则直接返回已有的实例对象。那创建的这个实例要保存到哪里呢?放在全局对象?No,一般的实现是通过立即执行函数和闭包实现实例的存储,然后通过对外方法来访问这个实例对象。

这样做的好处有:

  • 不会污染全局变量。
  • 外部无法对这个变量进行修改,保证了实例的“永久性”和“可靠性”。
  • 让变量彻底地私有。

为什么要用单例

如果单例用到恰当,那么它可以为你节省系统资源,可以让你很好的管理你的代码。

使用场景

比如:模态窗,一个页面一般情况下只会弹出一个,当我们多次触发按钮时,我们不应该每点一次都要生成一个模态窗,毕竟生成 DOM 并且添加到页面中这个过程还是挺费劲的(性能),还有一个就是如果每次都创建一个模态窗,那得在页面中添加多少个相关的元素?

退一步说,你在做这个弹窗时已经考虑到了避免在页面中生成多个模态窗元素,我想你的代码可能会是这样的:

把模态窗的所有 HTML 代码都创建好,直接放到页面中,通过样式把它隐藏起来(display:none),当用户点击按钮时把样式改成显示(display:block),这样做也不是不好,只是不够好。

我们再来想象这么一种场景:用户来到了这个页面,他只是想看看而已,而没有做其它的操作,比如:点击按钮触发模态窗。那我们之前为他准备好的模态窗是不是就多余了呢?不管你多么认真,做了别人不要的东西就是浪费,作为龙的传人,勤俭节约是我们的生活之道,所以我们有必需,也应该为此作出一些改变。

惰性单例

惰性单例是什么意思?也就是懒呗,不到最后一刻我都不干活。换句话说就是,在需要的时候我再为你创建一个弹窗,这样对你我都有好处。重要的是这样的“怠慢”不会影响了我们的感情,还是一见如故。

const LazySingleModal = (function(){
    let _instanceModal = null;
    return function(){
        if(!_instanceModal){ // 如果 _instanceModal 还是 null,说明是第一次创建
            // 假设 Modal 模态窗就只是一个 div 元素。
            const modal = document.createElement("div");
            modal.className = "single-modal";
            _instanceModal = modal;
        }
        // 返回单例
        return _instanceModal;
    }
})()

const m1 = LazySingleModal();
const m2 = LazySingleModal();
console.log(m1 === m2); // true

打印 true 说明 m1 和 m2 是指向了同一个对象。这也就说明了我们这个单例已经成功的实现了。

上面 LazySingleModal 的定义只是定义了一个单例,而当我们调用 LazySingleModal() 这个方法时才体现出了这个单例的惰性,因为我们在需要时才调用它来创建模态窗的,而换成我们的实践项目中,可能就是通过一个按钮来触发上面的函数(调用),这个按钮才是惰性的完美代言人。我只有按了这个按钮才给我生成一个模态窗,当我关闭模态窗(虽然上面的关闭按钮没有实现,但你可以意想一下)后,再次点击按钮时,你应该把之前那个模态窗实例找出来呈现给我,这才算是完成的惰性单例。

单例提能

我知道你会问,你直接把已有的模态窗实例直接返回给我又有什么用。毕竟我的标题,内容很可能都不一样。这又要怎么办呢?答案是我们可以给方法传参来解决这个问题。

const LazySingleModal = (function(){
    let _instanceModal = null;
    const doc = document; // 由于代码多处用到 document 所有把它存到局部变量
    // 设置标题
    function setTitle(str){
        const title = doc.createTextNode(str);
        _instanceModal.appendChild(title);
    }
    // 设置内容
    function setContent(str){
        const content = doc.createTextNode(str);
        _instanceModal.appendChild(content);
    }
    return function(title,content){
        if(!_instanceModal){ // 如果 _instanceModal 是 null,说明是第一次创建
            // 假设 Modal 模态窗就只是一个 div 元素。
            const modal = doc.createElement("div");
            modal.className = "single-modal";
            _instanceModal = modal;
            doc.body.appendChild(_instanceModal);
        }
        setTitle(title);
        setContent(content);
        // 返回单例
        return _instanceModal;
    }
})()

const m1 = LazySingleModal("标题1","内容1");
const m2 = LazySingleModal("标题2","内容2");

上面的代码是基于旧代码的修改。这样我们就完成了一个基于单例的可自定义模态窗,当然,在实际的开发中,这个模态窗还会有很多东西要加,这里只是给你提供一个单例的思路,毕竟授人以鱼不如授人以渔。只要你有了单例的思想,其它都可以在这个基本上慢慢添加。