单例模式
单例模式:又称单体模式,是javascript中最有用最基本的模式。
应用场景:单例模式是一种常用的模式,有一些对象我们往往只需要一个,比如线程池、全局缓存、浏览器中的window对象等。在JavaScript开发中,单例模式的用途同样非常广泛。试想一下,当我们单击登录按钮的时候,页面中会出现一个登录浮窗,而这个登录浮窗是唯一的,无论单击多少次登录按钮,这个浮窗都只会被创建一次,那么这个登录浮窗就适合用单例模式来创建。
单例模式实现的目的:确保只有一个实例,并提供全局访问。
全局变量可以达到以上目的,但是存在很多问题,会造成命名空间污染。如果代码中有很多变量,容易造成变量覆盖等问题。这时,单例模式显得尤为重要。
实现单利模式的方法有如下几种:
一、最简单的字面量方法
//使用字面量方式
var mySingleton = {
property1: "something",
property2: "something else",
method1: function () {
console.log('hello world');
}
};
var t1 = mySingleton;
var t2 = mySingleton;
alert( t1.property1);//something
t1.method1();//hello world
alert(t1===t2);//true
字面量实现单例模式的优点是简单实用,减少了变量和全局变量打交道的机会;但缺点也很明显,属性和方法都是暴露的,缺少封装性。对于一些需要使用私有变量的情况时,显然是不适用的。
二、构造函数内部判断
//构造函数内部判断方式
function Construct(){
if(Construct.unique!==undefined){
return Construct.unique;
}
this.property1 = 'something';
this.property2 = 'something else';
this.method1 = function (){
console.log('hello world');
}
Construct.unique = this;
}
var t1 = new Construct();
var t2 = new Construct();
alert(t1.property1);
alert(t2.property1);
t1.method1();
t2.method1();
alert(t1===t2);
用构造函数的方法实现单例模式,相当于创造了一个单例类,从单例类中创造对象,可以像其他普通类一样使用。但是这种方式也没有安全性,一旦我在外部修改了Construct的unique属性,那么单例模式也就被破坏了。
三、闭包方法(使用闭包封装私有变量)
这种方法把一些变量封装在闭包内部,只暴露一些接口跟外界通信。
var mySingleton = (function(){
var unique;
function Construct(){
// ... 生成单例的构造函数的代码
if(Construct.unique!==undefined){
return Construct.unique;
}
this.property1 = 'something';
this.property2 = 'something else';
this.method1 = function (){
console.log('hello world');
}
Construct.unique = this;
}
unique = new Construct();
/*公有变量和方法 */
return {
publicVariable :'something else',
publicMethod2 : function(){
alert(Construct().property1);
Construct().method1();
}
};
})();
var t1 = mySingleton;
var t2 = mySingleton;
alert(t1===t2);//true
t1.publicMethod2();//hello world
t2.publicMethod2();//hello world
使用闭包的方式实现单例模式,可以实现封装,相对安全。
闭包实现单例模式的方法多种多样,这只是其中一种方式。
实例:
下面以登录浮窗为例,我们的目的是点击登录,创造唯一的浮窗。
利用单例模式的代码如下:
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';
};
这段代码用单例模式实现了唯一浮窗,但是这段代码仍然是违反单一职责原则的,创建对象和管理单例的逻辑都放在createLoginLayer对象内部。如果要创造唯一的script或其他元素不具备复用性,下面来对代码进行优化,将不变的部分抽象出来。
代码如下:
var getSingle = function( fn ){
var result;
return function(){
return result || ( result = fn .apply(this, arguments ) );
}
};
var createLoginLayer = function(){
var div = document.createElement( 'div' );
div.innerHTML = '我是登录浮窗';
div.style.display = 'none';
document.body.appendChild( div );
return div;
};
var createSingleLoginLayer = getSingle( createLoginLayer );
document.getElementById( 'loginBtn' ).onclick = function(){
var loginLayer = createSingleLoginLayer();
loginLayer.style.display = 'block';
};
这时,如果要创建其他的,也可以很方便的创建。
var createSingleIframe = getSingle( function(){
var script = document.createElement ( 'script' );
document.body.appendChild( script );
return script;
});
单例模式优缺点:
优点:
1、提供了对唯一实例的受控访问。
2、由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
3、允许可变数目的实例
缺点:
1、由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
2、单例类的职责过重,在一定程度上违背了“单一职责原则”,可以优化。
总结:
这里主要了解了最简单的设计模式—单例模式,由于js的灵活性使得实现单例模式的方法多种多样,单例模式的核心是确保只有一个实例,并提供全局访问。使用数据缓存来存储该单例,用作判断单例是否已经生成,是单例模式主要的实现思路。