摘要:觀察構造函數的代碼,該構造函數實際上負責了兩件事情第一是創建對象和執行初始化方法,第二是保證只有一個對象。惰性單例在實際開發中非常有用,是單例模式的重點。
單例模式
單例模式的定義是:
保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。
單例模式是一種常用的模式,有一些對象我們往往只需要一個,比如線程池、全局緩存、瀏覽器的window對象等。例如,當我們點擊登錄按鈕時,頁面會彈出一個登錄懸浮窗,而這個登錄懸浮窗是唯一的,無論點擊多少次登錄按鈕,這個懸浮窗只會被創建一次,這時,這個懸浮窗就適合用單例模式來創建。
實現單例模式實現一個標準的單例模式,一般是用一個變量來標志當前是否已經為某個類創建過對象,若是,則在下一次獲取該類的實例時,直接返回之前創建的對象。
不透明的單例模式var Singleton = function(name){ this.name = name; this.instance = null; } Singleton.prototype.getName = function(){ console.log(this.name); }; Singleton.getInstance = function(name){ if(! this.instance){ this.instance = new Singleton(name); } return this.instance; }; var a = Singleton.getInstance("sin1"); var b = Singleton.getInstance("sin2"); console.log(a === b); // 輸出:true
我們通過Singleton.getInstance來獲取Singleton類的唯一對象,這種方式想對簡單,但有一個問題,就是增加了類的“不透明性”,Singleton類的使用者必須知道這是一個單例類,跟以往通過new xxx來獲取對象的方式不同,這里只能使用Singleton.getInstance來獲取對象。
透明的單例模式現在我們通過一段代碼來實現一個透明的單例類,用戶從這個類中創建對象的時候,可以像使用其他任何普通類一樣。
var createDiv = (function(){ var instance; var createDiv = function(html){ 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.body.appendChild(div); }; return createDiv; })(); var a = new createDiv("sin1"); var b = new createDiv("sin2"); console.log(a === b); // 輸出:true
為了把instance封裝起來,我們使用了自執行的匿名函數和閉包,并且讓這個匿名函數返回真正的Singleton構造方法,這增加了一些程序的復雜度,閱讀起來也不是很舒服。
觀察Singleton構造函數的代碼,該構造函數實際上負責了兩件事情:第一是創建對象和執行初始化init方法,第二是保證只有一個對象。這不符合設計原則中的“單一職責原則”,這是一種不好的做法。假設我們某天需要利用這個類,在頁面中創建很多個div,即讓這個類從單例類編程一個普通的可以產生多個實例的類,我們就得改寫createDiv構造函數,把控制創建唯一對象的那一段去掉,這種修改會給我們帶來不必要的煩惱。
用代理實現單例模式現在我們通過引入代理類的方法,來解決上面提到的問題。
var createDiv = function(html){ this.html = html; this.init(); }; createDiv.prototype.init = function(){ var div = document.createElement("div"); div.innerHTML = this.html; document.body.appendChild(div); }; // 引入代理類 proxySingletonCreateDiv var proxySingletonCreateDiv = (function(){ var instance; return function(html){ if(!instance){ instance = new createDiv(html); } return instance; } })(); var a = new proxySingletonCreateDiv("sin1"); var b = new proxySingletonCreateDiv("sin2");
我們把負責管理單例的邏輯移到了代理類proxySingletonCreateDiv中。這樣一來,createDiv就變成了一個普通的類,它跟proxySingletonCreateDiv組合起來就可以達到單例模式的效果;如果多帶帶使用,就作為一個普通的類,能產生多個實例對象。
JavaScript中的單例模式前面提到的單例模式的實現,更多的是接近傳統面向對象語言中的實現,單例對象從類中創建而來。在以類為中心的語言中,這是很自然的做法,比如在Java中,如果需要某個對象,就必須先定義一個類,對象總是從類中創建而來。
但JavaScript是一門無類語言,生搬單例模式的概念并無意義。在JavaScript中創建對象非常簡單,直接聲明即可。既然這樣,我們就沒有必要為它先創建一個類。
單例模式的核心是確保只有一個實例,并提供全局訪問。
全局變量不是單例模式,但在JavaScript開發中,我們經常會把全局變量當成單例模式來使用,例如var a = {}; 。
當用這種方式創建對象a時,對象a確實獨一無二。如果變量a被聲明在全局作用域下,則我們可以在代碼中的任何位置使用這個變量,全局變量自然能全局訪問。這樣就滿足了單例模式的兩個條件。
但是,全局變量存在一些問題:
容易造成命名空間污染;
在大型項目中,如果不加以限制和管理,程序中可能存在很多這樣的變量;
JavaScript中的變量很容易被不小心覆蓋。
因此,在使用全局變量時,我們要盡力降低它的污染,通過以下方式:
1.使用命名空間
適當地使用命名空間,并不會杜絕全局變量,但可以減少全局變量的數量。
最簡單的方法依然是用對象字面量的方式:
var namespace1 = { a: function(){ alert(1); }, b: function(){ alert(2); } };
把a和b都定義為namespace1的屬性,這樣可以減少變量和全局作用域打交道的機會。‘
另外,可以動態地創建命名空間,如:
var myApp = {}; myApp.namespace = function(name){ var parts = name.split("."); var current = myApp; for(var i in parts){ if(!current[parts[i]]){ current[parts[i]] = {}; } current = current[parts[i]]; } }; myApp.namespace("event"); myApp.namespace("dom.style");
上述代碼等價于:
var myApp = { event:{}, dom:{ style:{} } };
2.使用閉包封裝私有變量
這種方法把一些變量封裝在閉包的內部,只暴露一些接口跟外界通信:
var user = (function(){ var __name = "sin1"; var __age = 29; return { getUserInfo: function(){ return __name + "-" + __age; } } })();
我們用下劃線來約定私有變量__name和__age,它們被封裝在閉包產生的作用域中,外部是訪問不到這兩個變量的,這就避免了對全局的命名污染。
惰性單例惰性單例指的是在需要的時候才創建對象實例。惰性單例在實際開發中非常有用,是單例模式的重點。
我們在開頭寫的Singleton類就用過這種技術,instance實例對象總是在我們調用Singleton.getInstance的時候才被創建,而不是在頁面加載好的時候就創建。
實現惰性單例假設,在一個提供登錄功能(點擊登錄按鈕彈出一個登錄懸浮窗)的web頁面中,可能用戶在訪問過程中,根本不需要進行登錄操作,只需要瀏覽某些內容。所以,沒有必要在頁面加載好之后就馬上創建登錄懸浮窗,只需要當用戶點擊登錄按鈕的時候才開始創建登錄懸浮窗,實現代碼如下:
惰性單例
但這段代碼還是存在一些問題的:
這段代碼仍然是違反單一職責原則的,創建對象和管理單例的邏輯都放在createLoginLayer對象內部;
如果我們下次需要創建頁面中唯一的iframe,或者script標簽,必須得如法炮制,把createLoginLayer函數幾乎照抄一遍。
通用的惰性單例為了解決上面的問題,我們可以實現一段通用的惰性單例代碼:
惰性單例
上面的代碼,
把管理單例的邏輯抽象了出來:用一個變量來標志是否創建過對象,如果是,則在下次直接返回這個已經創建好的對象;
把如何管理單例的邏輯封裝在getSingle函數內部,創建對象的方法fn被當成參數動態傳入getSingle函數;
將創建登錄懸浮窗的方法傳入getSingle,還能傳入createIframe,createScript;
getSingle函數返回一個新的函數,并且用一個變量result來保存fn的計算結果,result變量在閉包中,永遠不會被銷毀,所以在將來的請求中,如果result已經被賦值,那么它將返回這個值。
單例模式的用途不止在于創建對象,比如我們通常渲染完頁面中的一個列表后,就要給這個列表綁定click事件,如果通過ajax動態往列表里追加數據,在使用事件代理的前提下,click事件實際上只需要在第一次渲染列表的時候就被綁定一次。
惰性單例
PS:本節內容為《JavaScript設計模式與開發實踐》第四章 筆記。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/86213.html
摘要:本系列為設計模式與開發實踐作者曾探學習總結,如想深入了解,請支持作者原版單例模式實現單例模式單例模式的定義是保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。 本系列為《JavaScript設計模式與開發實踐》(作者:曾探)學習總結,如想深入了解,請支持作者原版 單例模式 實現單例模式 單例模式的定義是:保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。單例模式是一種常用的模式...
摘要:引言本文摘自設計模式與開發實踐在傳統開發工程師眼里,單例就是保證一個類只有一個實例,實現的方法一般是先判斷實例存在與否,如果存在直接返回,如果不存在就創建了再返回,這就確保了一個類只有一個實例對象。 引言 本文摘自《JavaScript設計模式與開發實踐》 在傳統開發工程師眼里,單例就是保證一個類只有一個實例,實現的方法一般是先判斷實例存在與否,如果存在直接返回,如果不存在就創建了再返...
摘要:所以程序在引入文件的時候用了單例模式,一個文件實例化一次,這種做法無疑是好的,但是也容易引起。在我們平時的開發過程中,可以借鑒這兩種方式去緩存變量,節點等。 這一章作者講了一個例子,就是在用單例模式生成一個dom節點,還要做到只有訪問的時候才創建,后續訪問直接用前面創建的。那么實際開發中我們會用到這個模式嗎?現在我們基本都是用vue,react,angular開發,不太會直接去操作do...
摘要:訂閱模式的一個典型的應用就是后面會寫一篇相關的讀書筆記。享元模式享元模式的核心思想是對象復用,減少對象數量,減少內存開銷。適配器模式對目標函數進行數據參數轉化,使其符合目標函數所需要的格式。 設計模式 單例模式 JS的單例模式有別于傳統面向對象語言的單例模式,js作為一門無類的語言。使用全局變量的模式來實現單例模式思想。js里面的單例又分為普通單例和惰性單例,惰性單例指的是只有這個實例...
摘要:停更許久,近期計劃更新設計模式系列。單例模式是創建型設計模式的一種。雖然它不是正規的單例模式,但不可否認確實具備類單例模式的特點。適用場景單例模式的特點,意圖解決維護一個全局實例對象。 停更許久,近期計劃更新:設計模式系列。 showImg(https://segmentfault.com/img/bVbt7uw?w=800&h=600); 單例模式:限制類實例化次數只能一次,一個類只...
閱讀 2821·2023-04-26 02:00
閱讀 2776·2019-08-30 15:54
閱讀 868·2019-08-30 11:15
閱讀 1508·2019-08-29 15:31
閱讀 923·2019-08-29 14:12
閱讀 493·2019-08-29 13:08
閱讀 844·2019-08-27 10:51
閱讀 2712·2019-08-26 12:17