確保一個類僅有一個實例,並提供一個訪問它的全局訪問點。html
好比線程池、全局緩存等。咱們所熟知的瀏覽器的window對象就是一個單例,在JavaScript開發中,對於這種只須要一個的對象,咱們的實現每每使用單例。git
通常咱們是這樣實現單例的,用一個變量來標誌當前的類已經建立過對象,若是下次獲取當前類的實例時,直接返回以前建立的對象便可。代碼以下:github
// 定義一個類 function Singleton(name) { this.name = name; this.instance = null; } // 原型擴展類的一個方法getName() Singleton.prototype.getName = function() { console.log(this.name) }; // 獲取類的實例 Singleton.getInstance = function(name) { if(!this.instance) { this.instance = new Singleton(name); } return this.instance }; // 獲取對象1 var a = Singleton.getInstance('a'); // 獲取對象2 var b = Singleton.getInstance('b'); // 進行比較 console.log(a === b);
咱們也可使用閉包來實現:設計模式
function Singleton(name) { this.name = name; } // 原型擴展類的一個方法getName() Singleton.prototype.getName = function() { console.log(this.name) }; // 獲取類的實例 Singleton.getInstance = (function() { var instance = null; return function(name) { if(!this.instance) { this.instance = new Singleton(name); } return this.instance } })(); // 獲取對象1 var a = Singleton.getInstance('a'); // 獲取對象2 var b = Singleton.getInstance('b'); // 進行比較 console.log(a === b);
這個單例實現獲取對象的方式常常見於新手的寫法,這種方式獲取對象雖然簡單,可是這種實現方式不透明。知道的人能夠經過 Singleton.getInstance()
獲取對象,不知道的須要研究代碼的實現,這樣很差。這與咱們常見的用 new
關鍵字來獲取對象有出入,實際意義不大。瀏覽器
var Singleton = (function(){ var instance; var CreateSingleton = function (name) { this.name = name; if(instance) { return instance; } // 打印實例名字 this.getName(); // instance = this; // return instance; return instance = this; } // 獲取實例的名字 CreateSingleton.prototype.getName = function() { console.log(this.name) } return CreateSingleton; })(); // 建立實例對象1 var a = new Singleton('a'); // 建立實例對象2 var b = new Singleton('b'); console.log(a===b);
這種單例模式我之前用過一次,可是使用起來很彆扭,我也見過別人用這種方式實現過走馬燈的效果,由於走馬燈在咱們的應用中絕大多數只有一個。緩存
這裏先說一下爲何感受不對勁,由於在這個單例的構造函數中一共幹了兩件事,一個是建立對象並打印實例名字,另外一個是保證只有一個實例對象。這樣代碼量大的化不方便管理,應該儘可能作到職責單一。閉包
咱們一般會將代碼改爲下面這個樣子:app
// 單例構造函數 function CreateSingleton (name) { this.name = name; this.getName(); }; // 獲取實例的名字 CreateSingleton.prototype.getName = function() { console.log(this.name) }; // 單例對象 var Singleton = (function(){ var instance; return function (name) { if(!instance) { instance = new CreateSingleton(name); } return instance; } })(); // 建立實例對象1 var a = new Singleton('a'); // 建立實例對象2 var b = new Singleton('b'); console.log(a===b);
這種實現方式咱們就比較熟悉了,咱們在開發中常常會使用中間類,經過它來實現原類所不具備的特殊功能。有的人把這種實現方式叫作代理,這的確是單例模式的一種應用,稍後將在代理模式進行詳解。dom
說了這麼多咱們仍是在圍繞着傳統的單例模式實如今進行講解,那麼具備JavaScript特點的單例模式是什麼呢。函數
在咱們的開發中,不少同窗可能並不知道單例究竟是什麼,應該如何使用單例,可是他們所寫的代碼卻恰好知足了單例模式的要求。如要實現一個登錄彈窗,無論那個頁面或者在頁面的那個地方單擊登錄按鈕,都會彈出登陸窗。一些同窗就會寫一個全局的對象來實現登錄窗口功能,是的,這樣的確能夠實現所要求的登錄效果,也符合單例模式的要求,可是這種實現實際上是一個巧合,或者一個美麗的錯誤。因爲全局對象,或者說全局變量正好符合單例的可以全局訪問,並且是惟一的。可是咱們都知道,全局變量是能夠被覆蓋的,特別是對於初級開發人員來講,剛開始無論定義什麼基本都是全局的,這樣的好處是方便訪問,壞處是一不留意就會引發衝突,特別是在作一個團隊合做的大項目時,因此成熟的有經驗的開發人員儘可能減小全局的聲明。
而在開發中咱們避免全局變量污染的一般作法以下:
它們的共同點是均可以定義本身的成員、存儲數據。區別是全局命名空間的全部方法和屬性都是公共的,而閉包能夠實現方法和屬性的私有化。
說實話,在我下決心學習設計模式以前我並不知道,單例模式還分惰性單例模式,直到我看了曾探大神的《JvaScript設計模式與開發實踐》後才知道了還有惰性單例模式,那麼什麼是惰性單例模式呢?在說惰性單例模式以前,請容許我先說一個咱們都知道的lazyload加載圖片,它就是惰性加載,只當含有圖片資源的dom元素出如今媒體設備的可視區時,圖片資源纔會被加載,這種加載模式就是惰性加載;還有就是下拉刷新資源也是惰性加載,當你觸發下拉刷新事件資源纔會被加載等。而惰性單例模式的原理也是這樣的,只有當觸發建立實例對象時,實例對象纔會被建立。這樣的實例對象建立方式在開發中頗有必要的。
就如同咱們剛開始介紹的用 Singleton.getInstance
建立實例對象同樣,雖然這種方式實現了惰性單例,可是正如咱們剛開始說的那樣這並非一個好的實現方式。下面就來介紹一個好的實現方式。
遮罩層相信你們對它都不陌生。它在開發中比較常見,實現起來也比較簡單。在每一個人的開發中實現的方式不盡相同。這個最好的實現方式仍是用單例模式。有的人實現直接在頁面中加入一個div而後設置display爲none,這樣無論咱們是否使用遮罩層頁面都會加載這個div,若是是多個頁面就是多個div的開銷;也有的人使用js建立一個div,當須要時就用將其加入到body中,若是不須要就刪除,這樣頻繁地操做dom對頁面的性能也是一種消耗;還有的人是在前一種的基礎上用一個標識符來判斷,當遮罩層是第一次出現就向頁面添加,不須要時隱藏,若是不是就是用前一次的添加的。
實現代碼以下:
// html <button id="btn">click it</button> // js var createMask = (function() { var mask; return function() { if(!mask) { // 建立div元素 var mask = document.createElement('div'); // 設置樣式 mask.style.position = 'fixed'; mask.style.top = '0'; mask.style.right = '0'; mask.style.bottom = '0'; mask.style.left = '0'; mask.style.opacity = ''; mask.style.display = 'none'; document.body.appendChild(mask); } return mask; } })(); document.getElementById('btn').onclick = function() { var maskLayer = createMask(); maskLayer.style.display = 'block'; }
咱們發如今開發中並不會單獨使用遮罩層,遮罩層和彈出窗是常常結合在一塊兒使用,前面咱們提到過登錄彈窗使用單例模式實現也是最適合的。那麼咱們是否是要將上面的代碼拷貝一份呢?固然咱們還有好的實現方式,那就是將上面單例中代碼變化的部分和不變的部分,分離開來。
代碼以下:
var singleton = function(fn) { var instance; return function() { return instance || (instance = fn.apply(this, arguments)); } }; // 建立遮罩層 var createMask = function(){ // 建立div元素 var mask = document.createElement('div'); // 設置樣式 mask.style.position = 'fixed'; mask.style.top = '0'; mask.style.right = '0'; mask.style.bottom = '0'; mask.style.left = '0'; mask.style.opacity = 'o.75'; mask.style.backgroundColor = '#000'; mask.style.display = 'none'; mask.style.zIndex = '98'; document.body.appendChild(mask); // 單擊隱藏遮罩層 mask.onclick = function(){ this.style.display = 'none'; } return mask; }; // 建立登錄窗口 var createLogin = function() { // 建立div元素 var login = document.createElement('div'); // 設置樣式 login.style.position = 'fixed'; login.style.top = '50%'; login.style.left = '50%'; login.style.zIndex = '100'; login.style.display = 'none'; login.style.padding = '50px 80px'; login.style.backgroundColor = '#fff'; login.style.border = '1px solid #ccc'; login.style.borderRadius = '6px'; login.innerHTML = 'login it'; document.body.appendChild(login); return login; }; document.getElementById('btn').onclick = function() { var oMask = singleton(createMask)(); oMask.style.display = 'block'; var oLogin = singleton(createLogin)(); oLogin.style.display = 'block'; var w = parseInt(oLogin.clientWidth); var h = parseInt(oLogin.clientHeight); }
在上面的實現中將單例模式的惰性實現部分提取出來,實現了惰性實現代碼的複用,其中使用apply改變改變了fn內的this指向,使用 ||
預算簡化代碼的書寫。