Js单例模式

单例模式

简单的单例模式实现

需要用一个变量来标记是否已经创建过一个实例对象

var Single = function(name){
    this.name = name;
    this.instance = null;
}
Single.getInstance = function(name){
if(!this.instance){
    this.instace = new Single(name)
    }
return this.instance
}

var a = Single.getInstance("a");
var b = Single.getInstance("b");
a===b //true

缺点:不透明,相比于正常的 new 创建实例,创建单例必须用 getInstance 方法.

透明的单例模式

目标是实现一个透明的单例类,下面将使用 CreateDiv 单例类,在页面创建唯一的 div 节点

var CreateDiv = (function(){
    var instance = null;//自执行函数保证instance只初始化一次
    function CreateDiv(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.append(div);
    }

    return CreateDiv
})()
var a = new CreateDiv("a");
var b = new CreateDiv("b");
a===b//true

为了将 instance 封装起来使用了自执行匿名函数和闭包 增加了复杂性 并且 CreateDiv 负责了两件事 第一是创建对象和执行 init 方法 第二是保证只有一个对象 。违背了单一职责原则

用代理实现单例模式

为了解决上述的单一职责问题 下面引入代理模式实现这个单例模式
首先把创建对象和执行 init 方法分离出来

function  CreateDiv(html){
    this.html = html;
    this.init();
}
CreateDiv.prototype.init = function(html){
    var div = document.createElement("div");
    div.innerHTML = this.html;
    document.body.append(div);
}

接下来引入代理类

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

})()
var a = new createDivProx("a")
var b= new createDivProx("b")
a==b//true

Js 的单例模式-使用闭包封装的私有变量

既然我们需要的是一个对象 那就没必要先创建一个类

为了避免全局变量的滥用,可以把变量封装在闭包内部 只暴露接口和外界通信

var user = (function(){
    var _name = "yosgi"
    var _age = 29;
    return {
        getUserInfo(){
            return _name+"-"+_age
        }
    }
})()

或者使用 es6 的 Symbol

var _name = Symbol.for('name');
var _age = Symbol.for('age');
window[_name] = "yosgi"
window[_age] = 29//_name 和_user不会被无意中覆盖

完成一个惰性单例

惰性单例指在需要的时候才创建实例对象。

下面完成一个需求是 当点击 login 按钮时被创建的 div 框(以后也有可能是 iframe 框)。

 var createLoginLayer = (function () {
        var div;
        return function () {
            if (!div) {
                div = document.createElement("div");
                div.style.display = "none";
                document.body.appendChild(div);
            }
            return div
        }
    })()
    document.getElementById("login").onclick = function () {
        var LoginLayer = createLoginLayer()
        LoginLayer.style.display = 'block';
    };

为了解决单一职责的问题,将维护单一实例的代码提取出来

//getSingle用来管理单例
var getSingle = (function(){
    var result;
    return function(fn){
        if(!result){
        //闭包使result可以保存fn的结果
            result = fn.apply(this,arguments)
        }
        return result
    }
})()

//createSingleIframe用来创建实例
var createSingleIframe = function(){
    iframe = document.createElement("iframe");
    iframe.style.display = "none";
    document.body.appendChild(iframe);
    return iframe
}

document.getElementById("login").onclick = function(){
    var Iframe = getSingle(createSingleIframe)
    Iframe.style.display = 'block';
    Iframe.src="http://baidu.com"
};

单例模式的应用

在渲染页面中的一个列表之后,绑定 addEventListener click 事件 ,如果是通过 ajax 动态添加数据,实际上只需要在第一次渲染绑定一次,如果不想判断是第几次渲染 可以利用 getSingle

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


var bindEvent = function(){

        document.getElementById("div1").addEventListener("click",()=>{
            console.log("click")
        })
        return true
}
var render = function() {
    getSingle(bindEvent)
}
render()
render()
render()

可以注意到,点击 div1 多次也只会触发一次点击事件(当然也可以使用 onclick,一个 click 在同一时间只能指向唯一的对象 这点我以前没注意)