JavaScript 設計模式-單例模式

前言

在你的第一印象中,設計模式應該是什麼呢前端

JavaScript 發展的歷程中,前行者們從實踐中總結出了不少特定問題的解決方法。簡單來講,設計模式就是在某種場合下對特定問題的簡潔而又優雅的解決方案git

在以後的一段時間,我將記錄 JavaScript 中各類常見的設計模式。或許你對此已經得心應手,或許平時已在使用,可是對其概念並非特別熟悉,又或許只是對此有一些模糊的概念。那麼,相信這個系列必定會帶給你些許收穫github

在瞭解這些常見的模式以前,默認你已經至少掌握web

  • this
  • 閉包
  • 高階函數
  • 原型和原型鏈

瞭解它們,會讓你更加清晰的認識一個模式。固然,或許我以前所記錄的有關這方面的東西可以給你些許幫助 傳送門設計模式

若是文章中有出現紕漏、錯誤之處,還請看到的小夥伴多多指教,先行謝過緩存

下面,就讓咱們從它開始吧 -- 單例模式微信

概念

顧名思義,只有一個實例閉包

定義:保證一個類僅有一個實例,並提供一個訪問它的全局訪問點

看到這樣的定義,你的腦海中是否會冒出 全局變量 的概念呢。不能否認,全局變量是符合單例模式的概念的。可是,咱們一般不會也不該該將它做爲一個單例來使用,緣由有如下兩點:app

  • 全局命名污染
  • 不易維護,容易被重寫覆蓋

ES6 以前,咱們一般會使用一個構造函數來模擬一個類,如今咱們也能夠直接使用 class 關鍵字來建立一個類,雖然其本質也是原型函數

想要保證一個類僅有一個實例,咱們須要提供一個變量來標誌當前是否已經爲一個類建立過實例。因此,單例模式的核心就是:確保只有一個實例,並提供全局訪問

圍繞這個核心,咱們也就基本清楚了單例模式的實現方式

實現

基礎版

根據單例模式的定義,咱們能夠用如下方式簡單實現

var Singleton = function(name){
    this.name = name
}

Singleton.instance = null // 初始化一個變量

Singleton.getInstance = function(name) {
// 判斷這個變量是否已經被賦值,若是沒有就使之爲構造函數的實例化對象
// 若是已經被賦值了就直接返回
    if(!this.instance) {
        this.instance = new Singleton(name)
    }
    return this.instance
}

var a = Singleton.getInstance('Tadpole')
var b = Singleton.getInstance('Amy')

a === b // true

以上代碼,清晰的反映出了單例模式的定義。經過一箇中間變量的方式,只初始化一個實例,因此最終 ab 是徹底相等的

咱們也能夠用 ES6class 關鍵字來實現

class Singleton {
    constructor(name){
        this.name = name
        this.instance = null
    }
    // 提供一個接口對類進行實例化
    static getInstance(name) {
        if(!this.instance) {
            this.instance = new Singleton(name)
        }
        return this.instance
    }
}

不難發現,ES6 的實現方式和咱們經過構造函數的方式實現基本是一致的

存在問題:

  • 不夠 透明,咱們須要約束類實例化的調用方式
  • 耦合度太高,功能業務代碼耦合在一塊兒不利於後期維護

構造函數

讓咱們對上面的方式作一個簡單的修改

// 將變量直接掛在構造函數上面,最終將其返回
function Singleton(name) {
    if(typeof Singleton.instance === 'object') {
        return Singleton.instance
    }
    // 正常建立實例
    this.name = name
    return Singleton.instance = this
}

var a = new Singleton('Tadpole')
var b = new Singleton('Amy')

解決了基礎版類不夠 透明 的問題,可使用 new 關鍵字來初始化實例,但同時也存在着新的問題

  • 判斷 Single.instance 類型來返回,可能得不到預期結果
  • 耦合度太高

這種方式也能夠經過 ES6 方式來實現

// 將 constructor 改寫爲單例模式的構造器
class Singleton {
    constructor(name) {
        this.name = name
        if(!Singleton.instance) {
            Singleton.instance = this
        }
        return Singleton.instance
    }
}

閉包

經過單例模式的定義,想要保證只有一個實例而且能夠提供全局訪問。那麼,閉包確定也是能夠實現這樣的需求

var Singleton = (function () {
    var SingleClass = function () {}; 
    var instance; 
    return function () {
        if (!instance) { 
            instance = new SingleClass() // 若是不存在 則new一個
        }
        return instance;
    }
})()

經過閉包的特性,保存一個變量並最終將其返回,提供全局訪問

一樣的,以上的代碼仍是沒有解決耦合度的問題

讓咱們仔細觀察這一段代碼,若是咱們將其中構造函數的部分提取到外部,是否就實現了功能的分離呢

代理實現

修改一下上面的代碼

function Singleton(name) {
    this.name = name
}

var proxySingle = (function(){
    var instance
    return function(name) {
        if(!instance) {
            instance = new Singleton(name)
        }
        return instance
    }
})()

將建立函數的步驟從函數中提取出來,把負責管理單例的邏輯移到了代理類 proxySingle 中。這樣作的目的就是將 Singleton 這個類變成一個普通的類,咱們就能夠在其中單獨編寫一些業務邏輯,達到了邏輯分離的效果

咱們如今已經達到了邏輯分離的效果,而且也不 透明 了。可是,這個負責代理的類是否已經徹底符合咱們的要求呢,答案是否認的。設想一下,若是咱們的構造函數有多個參數,咱們是否是也應該在代理類中體現出來呢

那麼,有沒有更通用一些的實現方式呢

通用惰性單例

在前面的幾個回合,咱們已經基本完成了單例模式的建立。如今,咱們須要尋求一種更通用的方式解決以前留下來的問題

試想一下,若是咱們將函數做爲一個參數呢

// 將函數做爲一個參數傳遞
var Singleton = function(fn) {
    var instance
    return function() {
        // 經過apply的方式收集參數並執行傳入的參數將結果返回
        return instance || (instance = fn.apply(this, arguments))
    }
}

這種方式最大的優勢就是至關於緩存了咱們想要的結果,而且在咱們須要的時候纔去調用它,符合封裝的單一職責

應用

前面有說過,全部的模式都是從實踐中總結而來,下面就讓咱們來看看它在實際開發中都有哪些應用吧

經過單例模式的定義咱們不難想出它在實際開發中的用途,好比:全局遮罩層

一個全局的遮罩層咱們不可能每一次調用的時候都去建立它,最好的方式就是讓它只建立一次,以後用一個變量將它保存起來,再次調用的時候直接返回結果便可

單例模式就很符合咱們這樣的需求

// 模擬一個遮罩層
var createDiv = function () {
    var div = document.createElement('div')
    div.style.width = '100vw'
    div.style.height = '100vh'
    div.style.backgroundColor = 'red'
    document.body.appendChild(div)
    return div
}

// 建立出這個元素
var createSingleLayer = Singleton(createDiv)

document.getElementById('btn').onclick = function () {
    // 只有在調用的時候才展現
    var divLayer = createSingleLayer()
}

固然,在實際應用中仍是有不少適用場景的,好比登陸框,還有咱們可能會使用到的 Vux 之類的狀態管理工具,它們實際上都是契合單例模式的

後記

單例模式是一種簡單而又實用的模式,經過建立對象和管理單例的兩個方法,咱們就能夠創造出不少實用且優雅的應用。固然,它也有自身的缺點,好比只有一個實例~

合理使用才能發揮出它的最大威力

最後,推薦一波前端學習歷程,感興趣的小夥伴能夠 點擊這裏 ,也能夠掃描下方二維碼關注個人微信公衆號,查看往期更多內容,歡迎 star 關注

image

相關文章
相關標籤/搜索