淺析JavaScript設計模式——單例模式
單例模式
保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點
舉一個通俗的例子,在頁面中點選登入按鈕,彈出了一個登入浮窗,這個登入浮窗是唯一的,無論我們單擊多少次,浮窗只會建立一次
其實我們可能無意中都會使用過單例模式,我們的做法往往都是使用一個變數來標誌當前是否已經為某個類建立了物件,
如果true,那麼下一次再想獲得這個類的例項時,直接返回之前建立過的物件
單例模式的核心是確保只有一個例項,並提供全域性訪問
其實在JavaScript中,單例模式並沒有這麼複雜
因為我們沒有類
既然我們只要一個唯一的物件,為什麼要建立“類”呢
var a = {};
我們這樣建立了物件a,它確實獨一無二
而且滿足了單例模式的兩個條件
- 一個例項
- 全域性訪問
但是全域性變數很容易造成名稱空間汙染
如果專案很大的話,不小心覆蓋了變數那就是致命的
所以
在詳細講解這個單例模式之前,我們先來討論這樣一個問題,怎樣降低全域性汙染?
全域性汙染也就是變數大量存在於全域性作用域汙染了全域性空間
降低全域性汙染有兩種辦法
- 使用名稱空間
var namespace_payen = {
a: function(){
//...
}
b: function(){
//...
}
}
適當使用名稱空間,並不會杜絕全域性變數,但是可以減少全域性變數的數量
- 使用閉包封裝私有變數
var payen = (function(){
var _name = 'payson.Tsung',
_age = 19;
return {
getInfo: function(){
return _name + ' ' + _age;
}
}
})();
變數被封裝在了閉包內,只暴露一些介面用於外部通訊,從而避免了對全域性的命令汙染
下面我來談談這個單例模式
先來個簡單的例子
下面我聲明瞭一個函式,每次呼叫都建立一個小方塊
function createDiv(){
var div = document.createElement('div');
div.style.width = '100px';
div.style.height = '100px';
div.style.background = 'red';
div.style.marginBottom = '10px';
document.body.appendChild(div);
}
createDiv();
createDiv();
createDiv();
呼叫了三次,頁面出現了三個小方塊
下面我就使用單例模式,讓它只建立一個div
普通的使用bool變數的方法會汙染全域性空間這裡就不討論了╰( ̄▽ ̄)╭
var createDiv = (function(){
var div;
return function(){
if(!div){
div = document.createElement('div');
div.style.width = '100px';
div.style.height = '100px';
div.style.background = 'red';
div.style.marginBottom = '10px';
document.body.appendChild(div);
}
}
})();
createDiv();
createDiv();
createDiv();
再來看看頁面
只有一個小方塊
不知道大家看沒看懂這幾行程式碼
div宣告在了立即執行函式中作為私有變數
沒有執行函式前, div值為undefined
第一次執行函式時,判斷div有沒有,沒有於是建立了一個DOM節點並且插入到了文件
隨後再執行函式時,div變數已經快取了剛剛建立的DOM節點,於是不再建立
無論執行幾次,小方塊只會建立一次
這就是單例模式,而且是一個惰性單例
惰性單例就是在需要的時候才建立物件例項,而非在頁面載入時就建立
這樣做的好處大家都知道
雖然我們完成了惰性單例,但是我們同樣發現了問題
- 違反了單一職責原則,建立物件和管理單例放在了一個函式中createDiv
- 如果我們還想建立一個其他的唯一物件,那就只能copy了
綜上,我們需要
把不變的部分隔離出來,把可變的封裝起來,這給予了我們擴充套件程式的能力,符合開放-封閉原則
下面我們就抽出管理單例的邏輯
無論怎樣抽取,萬變不離其中,用一個變數來標誌是否建立過物件
var getSingle = function(fn){
var result;
return function(){
return result || (result = fn.apply(this, arguments));
}
};
var createDiv = function(){
var div = document.createElement('div');
div.style.width = '100px';
div.style.height = '100px';
div.style.background = 'red';
div.style.marginBottom = '10px';
document.body.appendChild(div);
return div;
};
var createSingleDiv = getSingle(createDiv);
createSingleDiv();
createSingleDiv();
createSingleDiv();
建立的DOM節點儲存在了result中
result變數因為自身在閉包中,不會被銷燬
再將來的請求中,如果result已經被賦值了,那麼它將返回這個值
相信大家可以看懂這幾行程式碼
大功告成
單例模式很簡單,而且也十分實用,他不僅僅用於建立物件,很有很多其他用途
比如說只繫結一次事件啦之類的
這個惰性單例模式建立物件和管理單例的職責分佈在兩個不同的方法,很棒
它並不難而且是非常實用的模式