1. 程式人生 > >[JavaScript設計模式] 什麼是單例模式

[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;
   }

實際上做了兩件事,

  1. 保證類只有一個例項
  2. 構造例項
    這樣就不符合單一職責原則,這個建構函式就變得不純粹了。如果哪天我不想使用單例了,我要在頁面建立普通的例項,建立多個不一樣的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類還是一個普通的建立例項的類,這樣保證了單一職責原則,組合起來就能達到單一職責原則。

以上就是單例類的建立模式,還有一類單例模式的應用,就是文章開頭說的登入框,點選登入按鈕,登入框會彈出,而無論點選多少次,登入框都只會彈出一個,這個登入框就是單例的,稍後我會新寫一篇文章分享這種單例模式的應用,敬請關注