JS 單例模式

《parctical common lisp》的做者曾說,若是你須要一種模式,那必定是哪裏出了問題。他所說的問題是指由於語言的天生缺陷,不得不去尋求和總結一種通用的解決方案。javascript

不論是弱類型或強類型,靜態或動態語言,命令式或說明式語言、每種語言都有天生的優缺點。一個牙買加運動員, 在短跑甚至拳擊方面有一些優點,在練瑜伽上就欠缺一些。java

術士和暗影牧師很容易成爲一個出色的輔助,而一個揹着梅肯滿地圖飛的敵法就會略顯尷尬。 換到程序中, 靜態語言裏可能須要花不少功夫來實現裝飾者,而js因爲能隨時往對象上面扔方法,以致於裝飾者模式在js裏成了雞肋。程序員

講javascript設計模式的書還比較少. Pro javaScript Design Patterns.是比較經典的一本,可是它裏面的例子舉得比較囉嗦,因此結合我在工做中寫過的代碼,把個人理解總結一下。若是個人理解出現了誤差,請不吝指正。web

一 單例模式設計模式

單例模式的定義是產生一個類的惟一實例,但js自己是一種「無類」語言。不少講js設計模式的文章把{}當成一個單例來使用也勉強說得通。由於js生成對象的方式有不少種,咱們來看下另外一種更有意義的單例。安全

有這樣一個常見的需求,點擊某個按鈕的時候須要在頁面彈出一個遮罩層。好比web.qq.com點擊登陸的時候.閉包

這個生成灰色背景遮罩層的代碼是很好寫的.app

var createMask = function(){

return document.body.appendChild( document.createElement('div') );

}dom


$( 'button' ).click( function(){

Var mask = createMask();

mask.show();

})函數

問題是, 這個遮罩層是全局惟一的, 那麼每次調用createMask都會建立一個新的div, 雖然能夠在隱藏遮罩層的把它remove掉. 但顯然這樣作不合理.

再看下第二種方案, 在頁面的一開始就建立好這個div. 而後用一個變量引用它.


var mask = document.body.appendChild( document.createElement( 'div' ) );

$( 'button' ).click( function(){

mask.show();

} )

這樣確實在頁面只會建立一個遮罩層div, 可是另一個問題隨之而來, 也許咱們永遠都不須要這個遮罩層, 那又浪費掉一個div, 對dom節點的任何操做都應該很是吝嗇.

若是能夠藉助一個變量. 來判斷是否已經建立過div呢?


var mask;

var createMask = function(){

if ( mask ) return mask;

else{

mask = document,body.appendChild( document.createElement('div') );

return mask;

}

}

看起來不錯, 到這裏的確完成了一個產生單列對象的函數. 咱們再仔細看這段代碼有什麼不妥.

首先這個函數是存在必定反作用的, 函數體內改變了外界變量mask的引用, 在多人協做的項目中, createMask是個不安全的函數. 另外一方面, mask這個全局變量並非非需不可. 再來改進一下.


var createMask = function(){
var mask;
return function(){
return mask || ( mask = document.body.appendChild( document.createElement('div') ) )
}
}()

用了個簡單的閉包把變量mask包起來, 至少對於createMask函數來說, 它是封閉的.

可能看到這裏, 會以爲單例模式也太簡單了. 的確一些設計模式都是很是簡單的, 即便從沒關注過設計模式的概念, 在平時的代碼中也不知不覺用到了一些設計模式. 就像多年前我明白老漢推車是什麼回事的時候也想過尼瑪原來這就是老漢推車.

GOF裏的23種設計模式, 也是在軟件開發中早就存在並反覆使用的模式. 若是程序員沒有明確意識到他使用過某些模式, 那麼下次他也許會錯過更合適的設計 (這段話來自《松本行弘的程序世界》).

再回來正題, 前面那個單例仍是有缺點. 它只能用於建立遮罩層. 假如我又須要寫一個函數, 用來建立一個惟一的xhr對象呢? 能不能找到一個通用的singleton包裝器.

js中函數是第一型, 意味着函數也能夠當參數傳遞. 看看最終的代碼.


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

var createMask = singleton(

function(){
return document.body.appendChild( document.createElement('div') );
}

)

用一個變量來保存第一次的返回值, 若是它已經被賦值過, 那麼在之後的調用中優先返回該變量. 而真正建立遮罩層的代碼是經過回調函數的方式傳人到singleton包裝器中的. 這種方式其實叫橋接模式. 關於橋接模式, 放在後面一點點來講.

然而singleton函數也不是完美的, 它始終仍是須要一個變量result來寄存div的引用. 遺憾的是js的函數式特性還不足以徹底的消除聲明和語句.

相關文章
相關標籤/搜索