前端设计模式学习笔记-单例模式

本文深入探讨单例模式的实现和应用,包括基本概念、实现方式、惰性单例技术及其实现,展示了如何在JavaScript中有效运用单例模式解决实际问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

单例模式的定义是,保证一个类仅有一个实例,并提供一个访问它的全局访问点。

单例模式是一种常用的模式,有一些对象我们只需要一个,比如线程池,全局缓存,浏览器window对象。当我们点击登录按钮的时候,页面中会出现一个登录浮窗,这个是唯一的,无论单击多少次,这个浮窗只会被创建一次,那么这样就适合用单例模式来创建。

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( 'sven1' ); 
var b = Singleton.getInstance( 'sven2' );
alert ( a === b ); // true

我们通过 Singleton.getInstance 来获取 Singleton 类的唯一对象,这种方式相对简单,但有 一个问题,就是增加了这个类的“不透明性”,Singleton 类的使用者必须知道这是一个单例类, 跟以往通过 new XXX 的方式来获取对象不同,这里偏要使用 Singleton.getInstance 来获取对象。

2.用代理实现单例模式

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

接下来引入代理类 proxySingletonCreateDiv: 

var ProxySingletonCreateDiv = (function(){
    var instance;
    return function( html ){
        if ( !instance ){
            instance = new CreateDiv( html );
        }
        return instance; }
    })();
var a = new ProxySingletonCreateDiv( 'sven1' ); 
var b = new ProxySingletonCreateDiv( 'sven2' );
alert( a === b );

通过引入代理类的方式,我们同样完成了一个单例模式的编写,跟之前不同的是,现在我们 把负责管理单例的逻辑移到了代理类 proxySingletonCreateDiv 中。这样一来,CreateDiv 就变成了 一个普通的类,它跟 proxySingletonCreateDiv 组合起来可以达到单例模式的效果。

3.JavaScript 中的单例模式

JavaScript 其实是一门无类(class-free)语言,也正因为如此,生搬单例模式的概念并无 意义。在 JavaScript 中创建对象的方法非常简单,既然我们只需要一个“唯一”的对象,为什 么要为它先创建一个“类”呢?这无异于穿棉衣洗澡,传统的单例模式实现在 JavaScript 中并 不适用。

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

全局变量不是单例模式,但在 JavaScript 开发中,我们经常会把全局变量当成单例来使用。

例如:

var a = {};

全局变量存在很多问题,它很容易造成命名空间污染

以下几种方式可以相对降低全局变量带来的命名污染。

1). 使用命名空间

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

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


var user = (function(){ 
    var __name = 'sven',
        __age = 29;
    return {
        getUserInfo: function(){
            return __name + '-' + __age; 
        }
    } 
})();

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

4.惰性单例

惰性单例指的睡在需要的时候才创建对象实例。惰性单例是单例模式的重点。这种技术在世纪开发中非常有用。

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

不过这是基于”类“的单例模式,基于'类'的单例模式在JavaScript中并不适用。

全局变量结合实现惰性的单例

当用户点击登录按钮的谁会才开始创建该浮窗

var createLoginLayer = (function(){ 
    var div;
    return function(){ 
        if ( !div ){
            div = document.createElement( 'div' ); 
            div.innerHTML = '我是登录浮窗';                 
            div.style.display = 'none'; 
            document.body.appendChild( div );
        }
        return div; 
    }
})();
document.getElementById( 'loginBtn' ).onclick = function(){ 
    var loginLayer = createLoginLayer(); 
    loginLayer.style.display = 'block';
};

5.通用的惰性单例

我们需要把不变的部分隔离出来,先不考虑创建一个 div 和创建一个 iframe 有多少差异,管 理单例的逻辑其实是完全可以抽象出来的,这个逻辑始终是一样的:用一个变量来标志是否创建 过对象,如果是,则在下次直接返回这个已经创建好的对象:

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

现在我们就把如何管理单例的逻辑从原来的代码中抽离出来,这些逻辑被封装在 getSingle 函数内部,创建对象的方法 fn 被当成参数动态传入 getSingle 函数:

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

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

var div = document.createElement( 'div' );
    div.innerHTML = '我是登录浮窗';
    div.style.display = 'none'; 2 document.body.appendChild( div );
    return div;
};
var createSingleLoginLayer = getSingle( createLoginLayer );

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

下面我们再试试创建唯一的 iframe 用于动态加载第三方页面:

var createSingleIframe = getSingle( function(){
    var iframe = document.createElement ( 'iframe' );
    document.body.appendChild( iframe );
    return iframe; 6
 });
document.getElementById( 'loginBtn' ).onclick = function(){ 
    var loginLayer = createSingleIframe();
    loginLayer.src = 'http://baidu.com';
};

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

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

var bindEvent = function(){
    $( 'div' ).one( 'click', function(){
        alert ( 'click' ); 
    });
};
var render = function(){ 
    console.log( '开始渲染列表' ); 
    bindEvent();
}; 

render();

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

var bindEvent = getSingle(function(){ 
    document.getElementById( 'div1' ).onclick = function(){
        alert ( 'click' ); 
    }
    return true; 
});
var render = function(){ 
    console.log( '开始渲染列表' ); 
    bindEvent();
};
render(); 
render(); 
render();

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值