介紹
- 單例模式的定義是:保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。
- 單例模式是一種常用的模式,有一些對象我們往往只需要一個,比如線程池、全局緩存、瀏覽器中的 window 對象等。在 JavaScript開發(fā)中,單例模式的用途同樣非常廣泛。試想一下,當我們單擊登錄按鈕的時候,頁面中會出現(xiàn)一個登錄浮窗,而這個登錄浮窗是唯一的,無論單擊多少次登錄按鈕,這個浮窗都只會被創(chuàng)建一次,那么這個登錄浮窗就適合用單例模式來創(chuàng)建。
正文
1.在JavaScript里,實現(xiàn)單例的方式有很多種,其中最簡單的一個方式是使用對象字面量的方法,其字面量里可以包含大量的屬性和方法:
var mySingleton = {
property1: "something",
property2: "something else",
method1: function () {
console.log('hello world');
}
};
2.使用閉包封裝私有變量,這種方法把一些變量封裝在閉包的內(nèi)部,只暴露一些接口跟外界通信:
var user = (function(){
var __name = 'sven',
__age = 29;
return {
getUserInfo: function(){
return __name + '-' + __age;
}
}
})();
我們用下劃線來約定私有變量 __name 和 __age ,它們被封裝在閉包產(chǎn)生的作用域中,外部是
訪問不到這兩個變量的,這就避免了對全局的命令污染。
惰性單例
惰性單例指的是在需要的時候才創(chuàng)建對象實例
<html>
<body>
<button id="loginBtn">登錄</button>
</body>
<script>
var createLoginLayer = function(){
var 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';
};
</script>
</html>
雖然現(xiàn)在達到了惰性的目的,但失去了單例的效果。當我們每次點擊登錄按鈕的時候,都會創(chuàng)建一個新的登錄浮窗 div 。雖然我們可以在點擊浮窗上的關(guān)閉按鈕時(此處未實現(xiàn))把這個浮窗從頁面中刪除掉,但這樣頻繁地創(chuàng)建和刪除節(jié)點明顯是不合理的,也是不必要的
我們可以用一個變量來判斷是否已經(jīng)創(chuàng)建過登錄浮窗,利用了閉包
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';
};
如果我們下次需要創(chuàng)建頁面中唯一的 iframe ,或者 script 標簽,用來跨域請求數(shù)據(jù),必須得如法炮制,把 createLoginLayer 函數(shù)幾乎照抄一遍:
var createIframe= (function(){
var iframe;
return function(){
if ( !iframe){
iframe= document.createElement( 'iframe' );
iframe.style.display = 'none';
document.body.appendChild( iframe);
}
return iframe;
}
})();
我們需要把不變的部分隔離出來,先不考慮創(chuàng)建一個 div 和創(chuàng)建一個 iframe 有多少差異,管
理單例的邏輯其實是完全可以抽象出來的,這個邏輯始終是一樣的:用一個變量來標志是否創(chuàng)建
過對象,如果是,則在下次直接返回這個已經(jīng)創(chuàng)建好的對象:
var obj;
if ( !obj ){
obj = xxx;
}
現(xiàn)在我們就把如何管理單例的邏輯從原來的代碼中抽離出來,這些邏輯被封裝在 getSingle
函數(shù)內(nèi)部,創(chuàng)建對象的方法 fn 被當成參數(shù)動態(tài)傳入 getSingle 函數(shù):
var getSingle = function( fn ){
var result;
return function(){
//如果存在result就直接返回,不存在就調(diào)用fn函數(shù)創(chuàng)建一個出來
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(){
//執(zhí)行g(shù)etSingle( createLoginLayer )函數(shù)
var loginLayer = createSingleLoginLayer();
loginLayer.style.display = 'block';
};
下面我們再試試創(chuàng)建唯一的iframe 用于動態(tài)加載第三方頁面:
var createSingleIframe = getSingle( function(){
var iframe = document.createElement ( 'iframe' );
document.body.appendChild( iframe );
return iframe;
});
document.getElementById( 'loginBtn' ).onclick = function(){
var loginLayer = createSingleIframe();
loginLayer.src = 'http://baidu.com';
};
在這個例子中,我們把創(chuàng)建實例對象的職責和管理單例的職責分別放置在兩個方法里,這兩個方法可以獨
立變化而互不影響,當它們連接在一起的時候,就完成了創(chuàng)建唯一實例對象的功能,看起來是一件挺奇妙的
事情。