JavaScript设计模式与开发实践_第四章_单例模式

第四章:单例模式


定义:保证一个类仅有一个实例,并提供一个访问它的全局变量。
在有些时候,一些对象我们往往只需要一个,比如线程池、全局缓存、浏览器中的Window对象等。
应用举例:单击登陆按钮的时候,页面中会出现一个唯一的登陆浮窗,无论单击多少次登陆按钮,这个浮窗也只会被创建一次,那么这个浮窗就适合使用单例模式来创建

4.1 实现单例模式

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


    var Singleton = function(name){
        this.name = name;
        this.instance = null;
    };

    Singleton.prototype.getName = function(){
        alert(this.name);
    };

    Singleton.getInstance = function(name){
        if(!this.instance){
            this.instance = new Singleton(name);
        }
        return this.instance;
    };


    var a =  Singleton.getInstance('seven1');
    var b =  Singleton.getInstance('seven2');

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

这里实现了一个简单的单例模式,但是这种方式增加了这个类的“不透明性”,Singleton类的使用者必须知道这是一个单例类,与以往通过new xxx来获取对象不同,这里要通过Singleton.getInstance()来获取对象


4.2 透明的单例模式

在页面中创建唯一的div节点


    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("seven1");
    var b = new CreateDiv("seven2");

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

解析:

  • 这里使用了匿名自执行函数与闭包。自执行函数主要是为了创建一个函数作用域,将CreateDiv对象的创建与instance合并。
  • 为了使通过new创建的对象的this能够指向唯一实例,使用return instance=this
  • 使用instance闭包保存唯一实例,如果instance不为空,返回instance;如果为空,就遵循new创建实例的规则,创建唯一实例,并将其送到instance中保存。

注意:

  • 闭包保存唯一实例
  • 每次new的时候,其实也会创建新的实例,但是,这个新的实例并不一定保存。
  • 主要看instance这个唯一实例的状态

4.3 通过代理实现单例


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

    // 代理
    var proxySingletonCreateDiv = (function(){
        var instance ;
        return function(html){
            if(!instance){
                instance = new CreateDiv(html);
            }
            return instance;
        }
    })();

    var a = new proxySingletonCreateDiv("seven1");
    var b = new proxySingletonCreateDiv("seven2");
    console.log(a === b);   // true

将负责管理单例的逻辑转移到了代理类proxySingletonCreateDiv中。
这样一来,CreateDiv就变成了一个普通的类,和proxySingletonCreateDiv组合起来可以达到单例模式的效果。
其实与上面的实现透明单例模式的代码差不多,只是在其基础之上做了功能的分离。
分离之后,如果某天需要用到CreateDiv这个类,在页面中创建超级多个div实例,就会很方便。
———— 这符合单一职责原则

4.4 JavaScript中的单例模式

上面几种单例模式,更多是接近传统面向对象语言中的实现,单例从“类”中创建而来。
然而JS其实是一门无类语言,在SJ中创建对象之前并不需要创建一个“类”

全局变量不是单例模式,但是在JavaScript开发中,经常把全局变量当作单例模式来使用
var a = {};
通过这种凡事创建的对象,独一无二且能在全局环境中使用,满足单例模式的两个条件。
但是全局变量中容易造成命名空间的污染

在我看来,完全可以利用const生成一个全局唯一变量。或者使用Promise也可以??只要保证全局唯一就好了。

4.4.1 使用命名空间

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

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

4.4.2 使用闭包封装私有变量

将一些变量封装在闭包内部,只暴露一些接口与外界通信

var user = (function(){
    var _name = 'Seven',
        _age = '29';
        
    return {
        getUserInfo:function(){
            return _name + "-" + _age;
        }
    }
})();

这样实现的单例,不需要通过额外的变量instance来判断是否需要创建构造函数的实例。通过user调用闭包中的数据,本身就是全局唯一的。

我觉得这并没有改善命名空间的污染。之前是在全局中定义了一个对象作为单例,对象名可能会出现变量污染,现在不过是换了一个作用域存储单例的数据内容,这个user同样可能会出现变量污染

4.5 / 惰性单例

惰性单例是单例模式的重点,在实际开发中非常有用。
特点:惰性单例是指在需要的时候才创建对象实例。比如开头第一段代码,instance总是在我们调用Singleton.getInstance的时候才会被创建,而不是在页面加载的时候就创建,那就是惰性单例。
不过,前面的代码是基于“类”的单例模式。而基于类的单例模式在JavaScript中并不适用。

例:登陆窗口

1、 登陆窗口总是在一开始就被创建好,只是通过css属性进行隐藏
2、 登陆窗口在点击登陆按钮的时候才被创建。

一开始就被创建好并没有必要,有的用户并没有登陆的需求,只需通过游客模式浏览一些信息,那么这样就会白白浪费一些DOM节点。

HTML:
<button class="login">登陆</button>
JavaScript:

var login = document.getElementsByClassName("login")[0];
    login.addEventListener("click",function(){
        var div = createLoginLayer();
        div.style.display = "block";
});

var createLoginLayer = (function(){
        var instance = null;

        var createLoginLayer = function(){
            if(instance){
                return instance;
            }
            
            var div = document.createElement("div");
            div.innerHTML = "我是登陆浮窗";
            div.style.display = "none";
            document.body.appendChild(div);

            return instance = div;
        };
        return createLoginLayer;

    })();

在这里插入图片描述
点击按钮会生成html结构并添加到页面中去。
不论点击多少次,只会生成一个登陆浮窗。

4.6 通用的惰性单例

  • 上面的代码虽然实现了一个可用的惰性单例,但是违反了单一职责原则,创建和管理单例的逻辑都放在createLoginLayer对象内部。
  • 如果我们并不是用来创建登陆浮窗,就需要将这个函数几乎照抄,修改要添加的HTML结构

要实现通用的惰性单例代码,需要将创建和管理单例的逻辑分离,并将管理单例的代码抽象出来,这部分一般是不变的。

HTML结构:
<button class="login">登陆</button>

JavaScript:
    
    // 创建HTML结构
    var createDiv = function(){
        var div = document.createElement("div");
        div.innerHTML = "Please Login In";
        div.style.display = "none";
        document.body.appendChild(div);

        return div;
    };
    
    // 单例控制
    var getSingle = (function(){
        var result ;
        return function(fn){
            if(result){
                return result;
            }
            return result = fn();



        };

    })();

    var login = document.getElementsByClassName("login")[0];


    login.onclick= function(){
        var div = getSingle(createDiv);

        div.style.display = "block"

    };

把创建实例对象的职责和管理单例的职责分别放置在两个方法里,这两个方法可以独立变化而互不影响,当它们连接在一起的时候,就完成了创建唯一实例对象的功能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值