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