JS單例模式《JavaScript設計模式與開發實踐》閱讀筆記

RoyLuo發表於2019-03-03

此文僅記錄本人閱讀《JavaScript設計模式與開發實踐》這個本時的感受,感謝作者曾探寫出這麼好的一本書。如有冒犯,如有錯誤,請聯絡本人:luogao_lg@sina.com處理。

這一章讓我知道了單例模式的核心就是:保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點。但在JavaScript中單例模式有別的區別於傳統面嚮物件語言的應用,惰性單例模式在實際的開發中有很多用途,例如提高頁面效能,避免不必要的DOM操作等。

為何要有單例模式

書中有舉出一個實際場景,當我們點選登陸按鈕時,頁面中可能會出現一個彈框,而這個彈框是唯一的,無論點多少次登陸按鈕,彈框只會被建立一次,那麼這種情況下就適合用單例模式來建立彈框。

實現一個簡單的單例模式

以下程式碼來自書中

var CreateDiv = (function(html) {
    var instance
    var CreateDiv = function() {
        if (instance) {
            return instance
        }
        this.html = html
        this.init()
        return instance = this
    }
    CreateDiv.prototype.init = function() {
        var div = document.createElement(`div`)
        div.innerHTML = this.html
        document.appendChild(div)
    }
    return CreateDiv
})()
複製程式碼

以上程式碼通過自執行函式和閉包將instance封裝起來。並且返回了真正的Singleton構造方法。

通過觀察上面程式碼發現CreateDiv裡執行了兩個操作:

  • 1.建立物件並且執行init方法。
  • 2.保證只有一個物件。這裡就暴露出一個問題。

如果某天我們需要用這個方法向頁面中建立更多的元素。那我們必須要改寫CreateDiv,如果我們結合“單一職責原則”,我們就知道要去把保證只有一個物件這個操作從CreateDiv抽離出來。這個目的可以通過代理來實現。

用代理實現單例模式

首先我們把上面程式碼中的CreateDiv方法改寫成一個只負責建立DIV的類

var CreateDiv = function(html) {
    this.html = html
    this.init()
}

CreateDiv.prototype.init = function() {
    var div = document.createElement(`div`)
    div.innerHTML = this.html
    document.appendChild(div)
}
複製程式碼

接下來引入代理類

var ProxysingletonCreateDiv = (function() {
    var instance
    return function(html) {
        if (!instance) {
            instance = new CreateDiv(html)
        }
        return instance
    }
})()
var a = new ProxysingletonCreateDiv(`test1`)
var b = new ProxysingletonCreateDiv(`test2`)

alert(a === b) // true
複製程式碼

至此利用代理類也實現了一個單例模式。但目前我們討論的單例模式跟接近傳統面嚮物件語言中的實現。接下來我們來了解一下JavaScript中的單例模式。

JavaScript中的單例模式——惰性單例

瞭解了單例模式的一些實現方法之後。我們可以來看看惰性單例的實現,這種實現方式在JavaScript的實際程式設計中是很實用的。

惰性單例

惰性單例是指在需要的時候才建立物件例項,而不是像之前的程式碼那樣,利用自執行函式在程式碼執行時就把物件例項建立。

比如最開始就提到,當開啟一個網站時,需要登入,但登陸的彈窗只會在點選登陸按鈕時出現,甚至有的網站不需要登入就能直接瀏覽。這時我們並不需要在頁面載入時就去建立一個彈窗。我們大可在需要用的時候去建立。

<html>
    <body>
        <button id="loginBtn">登入</button>
    </body>
    <script>
        var createLoginLayer = (function() {
            var div
            return function() {
                if (!div) {
                    var div = document.createElement(`div`)
                    div.innerHTML = `我是登入彈窗`
                    div.style.display = `none`
                    document.appendChild(div)
                }
                return div
            }
        })()
        document.getElementById(`loginBtn`).onclick = function() {
            var loginLayer = createLoginLayer()
            loginLayer.style.display = `block`
        }
    </script>
</html>
複製程式碼

以上我們實現了一個單例模式的彈窗。但是我們還是可以把其中的控制只有一個物件的操作抽離出來,讓我們來實現一個通用的惰性單例。

通用惰性單例

通用惰性單例的實現就是要抽離所有單例模式都要實現的——控制只有一個物件。那麼我們來看看控制只有一個物件的操作抽象出來是個什麼樣子:

var obj 
if (!obj) {
    obj = xxx
}
複製程式碼

於是就可以把這個操作的邏輯封裝到一個getSingle函式中,然後把要執行的函式當作引數傳入進去:

var getSingle = function(fn) {
    var result
    return function() {
        result || (result = fn.apply(this, arguments))
    }
}
複製程式碼

這樣我們上面寫的建立彈窗的方法就可以完全抽離出來:

var createLoginLayer = function() {
    var div = document.createElement(`div`)
    div.innerHTML = `我是登入彈窗`
    div.style.display = `none`
    document.appendChild(div)
    return div
}

var createSingleLoginLayer = getsingle(createLoginLayer)

document.getElementById(`loginBtn`).onclick = function() {
    var loginLayer = createSingleLoginLayer()
    loginLayer.style.display = `block`
}
複製程式碼

至此我們實現了一個getSingle函式來幫我們實現只有一個例項物件的目的,並且將例項物件要做的指責獨立出來,兩個方法互不打擾。

最後

原文出自 Roy`s Blog

參考

《JavaScript設計模式與開發實踐》—— 曾探

相關文章