您好,登錄后才能下訂單哦!
本篇文章將介紹JavaScript設計模式中的單例模式。有一定的參考價值,閱讀完整文相信大家對JavaScript設計模式中的單例模式有了一定的認識。
概念
顧名思義,只有一個實例
定義:保證一個類僅有一個實例,并提供一個訪問它的全局訪問點
看到這樣的定義,你的腦海中是否會冒出 全局變量 的概念呢。不可否認,全局變量是符合單例模式的概念的。但是,我們通常不會也不應該將它作為一個單例來使用,原因有以下兩點:
全局命名污染
不易維護,容易被重寫覆蓋
在 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
以上代碼,清晰的反映出了單例模式的定義。通過一個中間變量的方式,只初始化一個實例,所以最終 a 和 b 是完全相等的
我們也可以用 ES6 的 class 關鍵字來實現
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 之類的狀態管理工具,它們實際上都是契合單例模式的
后記
單例模式是一種簡單而又實用的模式,通過創建對象和管理單例的兩個方法,我們就可以創造出很多實用且優雅的應用。當然,它也有自身的缺點,比如只有一個實例。
看完上述內容,你們對JavaScript單例模式有進一步的了解嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。