JavaScript設(shè)計模式之單例模式實例
《Practical Common Lisp》的作者 Peter Seibel 曾說,如果你需要一種模式,那一定是哪里出了問題。他所說的問題是指因為語言的天生缺陷,不得不去尋求和總結(jié)一種通用的解決方案。
不管是弱類型或強(qiáng)類型,靜態(tài)或動態(tài)語言,命令式或說明式語言、每種語言都有天生的優(yōu)缺點。一個牙買加運動員, 在短跑甚至拳擊方面有一些優(yōu)勢,在練瑜伽上就欠缺一些。
術(shù)士和暗影牧師很容易成為一個出色的輔助,而一個背著梅肯滿地圖飛的敵法就會略顯尷尬。 換到程序中, 靜態(tài)語言里可能需要花很多功夫來實現(xiàn)裝飾者,而js由于能隨時往對象上面扔方法,以至于裝飾者模式在js里成了雞肋。
講 Javascript 設(shè)計模式的書還比較少,《Pro javaScript Design Patterns》是比較經(jīng)典的一本,但是它里面的例子舉得比較啰嗦,所以結(jié)合我在工作中寫過的代碼,把我的理解總結(jié)一下。如果我的理解出現(xiàn)了偏差,請不吝指正。
一、單例模式
單例模式的定義是產(chǎn)生一個類的唯一實例,但js本身是一種“無類”語言。很多講js設(shè)計模式的文章把{}當(dāng)成一個單例來使用也勉強(qiáng)說得通。因為js生成對象的方式有很多種,我們來看下另一種更有意義的單例。
有這樣一個常見的需求,點擊某個按鈕的時候需要在頁面彈出一個遮罩層。比如web.qq.com點擊登錄的時候.
這個生成灰色背景遮罩層的代碼是很好寫的.
var createMask = function(){
return document,body.appendChild( document.createElement(div) );
}
$( ‘button' ).click( function(){
Var mask = createMask();
mask.show();
})
問題是, 這個遮罩層是全局唯一的, 那么每次調(diào)用createMask都會創(chuàng)建一個新的div, 雖然可以在隱藏遮罩層的把它remove掉. 但顯然這樣做不合理.
再看下第二種方案, 在頁面的一開始就創(chuàng)建好這個div. 然后用一個變量引用它.
var mask = document.body.appendChild( document.createElement( ”div' ) );
$( ”button' ).click( function(){
mask.show();
} )
這樣確實在頁面只會創(chuàng)建一個遮罩層div, 但是另外一個問題隨之而來, 也許我們永遠(yuǎn)都不需要這個遮罩層, 那又浪費掉一個div, 對dom節(jié)點的任何操作都應(yīng)該非常吝嗇.
如果可以借助一個變量. 來判斷是否已經(jīng)創(chuàng)建過div呢?
var mask;
var createMask = function(){
if ( mask ) return mask;
else{
mask = document,body.appendChild( document.createElement(div) );
return mask;
}
}
看起來不錯, 到這里的確完成了一個產(chǎn)生單列對象的函數(shù). 我們再仔細(xì)看這段代碼有什么不妥.
首先這個函數(shù)是存在一定副作用的, 函數(shù)體內(nèi)改變了外界變量mask的引用, 在多人協(xié)作的項目中, createMask是個不安全的函數(shù). 另一方面, mask這個全局變量并不是非需不可. 再來改進(jìn)一下.
var createMask = function(){
var mask;
return function(){
return mask || ( mask = document.body.appendChild( document.createElement(‘div') ) )
}
}()
用了個簡單的閉包把變量mask包起來, 至少對于createMask函數(shù)來講, 它是封閉的.
可能看到這里, 會覺得單例模式也太簡單了. 的確一些設(shè)計模式都是非常簡單的, 即使從沒關(guān)注過設(shè)計模式的概念, 在平時的代碼中也不知不覺用到了一些設(shè)計模式. 就像多年前我明白老漢推車是什么回事的時候也想過尼瑪原來這就是老漢推車.
GOF里的23種設(shè)計模式, 也是在軟件開發(fā)中早就存在并反復(fù)使用的模式. 如果程序員沒有明確意識到他使用過某些模式, 那么下次他也許會錯過更合適的設(shè)計 (這段話來自《松本行弘的程序世界》).
再回來正題, 前面那個單例還是有缺點. 它只能用于創(chuàng)建遮罩層. 假如我又需要寫一個函數(shù), 用來創(chuàng)建一個唯一的xhr對象呢? 能不能找到一個通用的singleton包裝器.
js中函數(shù)是第一型, 意味著函數(shù)也可以當(dāng)參數(shù)傳遞. 看看最終的代碼.
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') );
})
用一個變量來保存第一次的返回值, 如果它已經(jīng)被賦值過, 那么在以后的調(diào)用中優(yōu)先返回該變量. 而真正創(chuàng)建遮罩層的代碼是通過回調(diào)函數(shù)的方式傳人到singleton包裝器中的. 這種方式其實叫橋接模式. 關(guān)于橋接模式, 放在后面一點點來說.
然而singleton函數(shù)也不是完美的, 它始終還是需要一個變量result來寄存div的引用. 遺憾的是js的函數(shù)式特性還不足以完全的消除聲明和語句.
相關(guān)文章
JS實現(xiàn)將人民幣金額轉(zhuǎn)換為大寫的示例代碼
本篇文章主要是對使用JS實現(xiàn)將人民幣金額轉(zhuǎn)換為大寫的示例代碼進(jìn)行了介紹,需要的朋友可以過來參考下,希望對大家有所幫助2014-02-02JavaScript實現(xiàn)字符串轉(zhuǎn)數(shù)組的6種方法總結(jié)
數(shù)組是?JavaScript?中最強(qiáng)大的數(shù)據(jù)結(jié)構(gòu),我們常常通過將字符串轉(zhuǎn)換為數(shù)組來解決許多算法。本文為大家總結(jié)了6個JS字符串轉(zhuǎn)數(shù)組的方法,希望對你有所幫助2022-09-0920170918 前端開發(fā)周報之JS前端開發(fā)必看
本文給大家分享了最新版js 前端開發(fā)周報,內(nèi)容非常不錯,具有參考借鑒價值,需要的朋友參考下吧2017-09-09Javascript 靜態(tài)頁面實現(xiàn)隨機(jī)顯示廣告的辦法
最近在做私服發(fā)布站時,客戶要求實現(xiàn)廣告隨機(jī)排序,而且要求在html頁面實現(xiàn),也就是說必須使用javascript來完成了。2010-11-11layuiAdmin循環(huán)遍歷展示商品圖片列表的方法
今天小編就為大家分享一篇layuiAdmin循環(huán)遍歷展示商品圖片列表的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-09-09ES6 exports與import導(dǎo)出模塊使用基礎(chǔ)示例
這篇文章主要為大家介紹了ES6 exports與import導(dǎo)出模塊使用基礎(chǔ)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06