javaScript中的单例模式

单例模式的定义:保证一个类仅有一个实例,并提供一个访问他的全局访问点。实现的方法为先判断实例存在与否,如果存在则直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。

在 JavaScript 中,单例模式的用途非常广泛,试想一下,当我们单击登陆按钮的时候,页面中会出现一个登录浮窗,而这个登录浮窗是唯一的,无论单机多少次登录按钮,这个浮窗都只会被创建一次,那么这个登录浮窗就适合用单例模式来创建。

用代理实现单例模式:

class CreateUser{
    constructor(name){
        this.name = name;
        this.getName();
    }
    getName(){
        return this.name;
    }
}

//用代理实现单例模式
var proxyMode = (function(){
    var instance = null;
    return  function(name){
        if(!instance){
            instance = new CreateUser(name);
        }
        return instance;
    }
})();

//测试
var a = new proxyMode('aaa');
var b = new proxyMode('bbb');
//因为单例模式是只实例化一次,所以下面的实例是相等的
console.log(a===b);

JS 中的单例模式:

上面这种方式更接近传统面向对象语言中的实现,单例对象从 “类”中创建而来。在以类为中心的语言中,这是很自然的做法。比如 java 中,如果需要某个对象就必须先定义一个类,对象总是从类中创建而来的。

但 JS 其实是一门无类(class-free) 语言,也正因为如此,生搬单例模式的概念并无意义。在 JS 中创建对象的方法非常简单,既然我们只需要一个“唯一”的对象,为什么要为它先创建一个“类”呢?

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

全局变量不是单例模式,但是在 JavaScript 开发中,我们经常会把全局变量当成单例来使用,例如:var a = {};但是全局变量存在很多问题,他很容易造成命名空间的污染。我们有必要尽量减少全局变量的使用,即使需要,也要把它的污染降到最低。一下几种方式可以相对降低屈居变量带来的污染。

1.使用命名空间

适当地使用命名空间,并不会杜绝全局变量,但可以减少全局变量的数量。最简单的方式俨然是使用对象字面量的方式:

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

把 a 和 b 都定义为 nameSpace1 的属性,这样可以减少变量和全局作用域打交道的机会,另外我们还可以动态的创建命名空间:

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

//等同于:
var myApp = {
    event:{},
    dom:{
        style:{}
    }
    
}

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

种种方法把一些变量封装在闭包的内部,只暴露一些接口跟外界通信:

var user = (function(){
    var __name = 'sven',
        __age = 29;

    return {
        getUserInfo:function(){
            return __name + '-' + __age;
        }
    }
})()

我们用下划线来约定私有变量 __name 和 __age,它们被封装在壁报产生的作用域中,外部是访问不到这两个变量的,这就避免了对全局的命名污染。

惰性单例

惰性单利指的是在需要的时候才创建对象实例。比如我们的弹出层,当用户不需要登录的时候,你创建了弹窗就造成了浪费。只有当用户需要的时候再进行创建就可以了。

例如:

var createLayer = (function(){
    var div = null;
    return function(){
        if(!div){
            var div = document.createElement('div');
            div.innerHTML = 'pop';
            div.style.display = 'none'
            document.body.appendChild(div);
        }

        return div;
    }
})()

document.getElementById('btn').onclick = function(){
    var loginLayer = createLayer();
    loginLayer.style.display = 'block';
}

通用的惰性单例

上面我们完成的可用的惰性单例,但是有如下问题:

  • 这段代码仍然是违反单一职责原则的,创建对象和管理单例的逻辑都放在 createLayer 对象内部。
  • 如果我们下次需要创建页面中唯一的 iframe ,或者 script 标签,用来跨域请求数据,就必须得如法炮制,把 createLayer 函数几乎照抄一遍。
var createIframe = (function(){
    var iframe = null;
    return function(){
        if(!iframe){
            var iframe = document.createElement('iframe');
            iframe.innerHTML = 'pop';
            iframe.style.display = 'none'
            document.body.appendChild(iframe);
        }

        return iframe;
    }
})()

我们需要把创建单例的逻辑抽离出来。因为单例逻辑始终是一样的:用一个变量来标志是否创建过对象,如果是,则下次直接返回这个已经创建好的对象:

var obj;
if(!obj){
    obj = xxx;
}

现在我们把管理单例的逻辑抽离封装在 getSingle 函数内部,创建对象的方法 fn 被当作参数动态传入 getSingle 中:

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

接下来将用于创建登录浮窗的方法用参数 fn  的形式传入 getSingle,我们不仅可以传递 createLayer,还能传入 createIframe、createScript 等,之后再让 getSingle 返回一个新函数,并且用一个变量 result 来保持 fn 的计算结果。result 变量因为身在闭包中,永远不会被销毁。在将来的请求中,如果 result 已经被赋值,那么它将返回这个值。

代码如下:

var createLayer = function(){
    var div = document.createElement('div');
    div.innerHTML = 'pop';
    div.style.display = 'none'
    document.body.appendChild(div);
    return div;
    
}

var createSingleLayer = getSingle(createLayer);

document.getElementById('btn').onclick = function(){
    var loginLayer = createSingleLayer();
    loginLayer.style.display = 'block';
}

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

这种单例模式的用途远不止创建对象,比如我们通常渲染完页面中的一个列表之后,接下来要给这个列表绑定 click 事件,如果是通过 ajax 动态往列表里追加数据,在使用事件代理的前提下,click 事件实际上只需要在第一次渲染列表的时候被绑定一次,但是我们不想去判断当前是否第一次渲染列表,如果借助 jq,我们通常选择给节点绑定 one 事件:

var bindEvent = function(){
    $('div').one('click',function(){
        alert('click');
    });
};

var render = function(){
    console.log('渲染列表');
    bindEvent();
};

render();

render();

render();

如果利用 getSingle 函数,也能达到一样的效果。代码如下:

var bindEvent = getSingle(function(){
    document.getElementById('div1').addEventListener = function(){
        alert('click');
    }
    return true;
});

var render = functiuon(){
    console.log('开始渲染列表');
    bindEvent();
};

render();
render();
render();

可以看到,render 函数和 bindEvent 函数都分别被执行了 3 次,但 div 实际上只被绑定了一个事件。

在 getSingle 函数中,实际上也提到了闭包和高阶函数的概念。单例模式是一种简单但非常有用的模式,特别是惰性单例技术,在合适的时候才创建对象,并且只创建唯一的一个。创建对象和管理单例的职责被分布在了两个不同的方法中,这两个方法组合起来才具有单例模式的的威力。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值