[JavaScript設計模式] 什麼是單例模式
概念
保證一個類僅有一個例項,並提供一個全域性訪問點
為什麼要用單例模式
想象一下某些web應用,當點選登入按鈕時,會彈出一個登入框,無論你點選多少次這個登入按鈕,登入框都只會出現一個,不會出現多個登入框。同時不會頻繁的進行刪除和新增,而是同一個登入框進行隱藏和顯示,因為刪除和新增十分耗費效能,所以單例可以達到最大化的效能利用。登入框這個例子就是單例模式最典型的應用,符合業務的需求,又能夠提高效能
單例模式的實現
簡單的單例模式
一個簡單的單例模式,無非就是用一個變數指示要建立的例項是否已經建立過了,如果已經建立過了,則在下一次使用例項時,直接返回複用,如果沒有建立過,則建立並儲存到變數中。
const SingleTon = function(name){ this.name = name; } SingleTon.prototype.getName = function (){ return this.name; } SingleTon.instance = null; SingleTon.getInstance = function(name){ if(!this.instance){ this.instance = new SingleTon(name); } return this.instance; } const aaa = SingleTon.getInstance("aaa"); const bbb = SingleTon.getInstance("bbb"); aaa.getName() === bbb.getName(); // true
但是這時候的這個單例類是“不透明”的,因為我們通常習慣使用new XXX()
的方式來例項化一個類,而不是通過使用者不知道的XXX.getInstance()
的方式來獲取單例物件。
所以我們應該使用透明的單例類
透明的單例類
先介紹下IIFE
IIFE( 立即呼叫函式表示式)是一個在定義時就會立即執行的 JavaScript 函式 --- MDN
eg.
const foo = (function(){ return "I am foo" })() console.log(foo); // "I am foo"
之所以使用IIFE,就是限制JS的變數作用域,在ES5中,沒有塊級作用域,只有函式作用域一種,所以提供一種模擬塊級作用域的方法就是IIFE。我們的透明單例模式就是使用IIFE來模擬。
const CreateDiv = (function(){
let instance = null;
const CreateDiv = function(html){
if(instance){
return instance;
}
this.html = html;
instance = this;
return instance;
}
CreateDiv.prototype.init = function(){
const div = document.createElement("div");
div.innerHTML = this.html;
document.body.appendChild(div);
}
return CreateDiv;
})();
const aaa = new CreateDiv("aaa");
const bbb = new CreateDiv("bbb");
aaa.init();
bbb.init();
使用IIFE來避免變數汙染,把在IIFE中建立的類return出去,供外部通過new XXX()
的形式呼叫。缺點是增加了程式的複雜度,不利於閱讀。
在建構函式CreateDiv中:
const CreateDiv = function(html){
if(instance){
return instance;
}
this.html = html;
instance = this;
return instance;
}
實際上做了兩件事,
- 保證類只有一個例項
- 構造例項
這樣就不符合單一職責原則,這個建構函式就變得不純粹了。如果哪天我不想使用單例了,我要在頁面建立普通的例項,建立多個不一樣的div,還需要去修改CreateDiv建構函式,去掉控制單一例項的那一段,但頁面中使用此單例的程式碼可能就用不了,故會帶來不必要的麻煩。所以我們應該使用單例代理。
使用代理的單例類
上面的問題我們可以用單例代理來解決,通過引入一個代理類,代理普通CreateDiv類,使之變為單例類。
const CreateDiv = function (html) {
this.html = html;
}
CreateDiv.prototype.init = function () {
const div = document.createElement("div");
div.innerHTML = this.html;
document.body.appendChild(div);
}
const ProxyCreateDiv = (function () {
let instance = null;
return function (html) {
if (!instance) {
instance = new CreateDiv(html);
}
return instance;
}
})();
const aaa = new ProxyCreateDiv("aaa");
const bbb = new ProxyCreateDiv("bbb");
aaa.init(); // aaa
bbb.init(); // aaa
我們把負責單例類管理的邏輯放到了單獨的ProxyCreateDiv類中,CreateDiv類還是一個普通的建立例項的類,這樣保證了單一職責原則,組合起來就能達到單一職責原則。
以上就是單例類的建立模式,還有一類單例模式的應用,就是文章開頭說的登入框,點選登入按鈕,登入框會彈出,而無論點選多少次,登入框都只會彈出一個,這個登入框就是單例的,稍後我會新寫一篇文章分享這種單例模式的應用,敬請關注