總結(jié)JavaScript設(shè)計(jì)模式編程中的享元模式使用
享元模式不同于一般的設(shè)計(jì)模式,它主要用來(lái)優(yōu)化程序的性能,它最適合解決大量類似的對(duì)象而產(chǎn)生的性能問(wèn)題。享元模式通過(guò)分析應(yīng)用程序的對(duì)象,將其解析為內(nèi)在數(shù)據(jù)和外在數(shù)據(jù),減少對(duì)象的數(shù)量,從而提高應(yīng)用程序的性能。
基本知識(shí)
享元模式通過(guò)共享大量的細(xì)粒度的對(duì)象,減少對(duì)象的數(shù)量,從而減少對(duì)象的內(nèi)存,提高應(yīng)用程序的性能。其基本思想就是分解現(xiàn)有類似對(duì)象的組成,將其展開(kāi)為可以共享的內(nèi)在數(shù)據(jù)和不可共享的外在數(shù)據(jù),我們稱內(nèi)在數(shù)據(jù)的對(duì)象為享元對(duì)象。通常還需要一個(gè)工廠類來(lái)維護(hù)內(nèi)在數(shù)據(jù)。
在JS中,享元模式主要有下面幾個(gè)角色組成:
(1)客戶端:用來(lái)調(diào)用享元工廠來(lái)獲取內(nèi)在數(shù)據(jù)的類,通常是應(yīng)用程序所需的對(duì)象,
(2)享元工廠:用來(lái)維護(hù)享元數(shù)據(jù)的類
(3)享元類:保持內(nèi)在數(shù)據(jù)的類
享元模式的實(shí)現(xiàn)和應(yīng)用
一般實(shí)現(xiàn)
我們舉個(gè)例子進(jìn)行說(shuō)明:蘋(píng)果公司批量生產(chǎn)iphone,iphone的大部分?jǐn)?shù)據(jù)比如型號(hào),屏幕都是一樣,少數(shù)部分?jǐn)?shù)據(jù)比如內(nèi)存有分16G,32G等。未使用享元模式前,我們寫(xiě)代碼如下:
function Iphone(model, screen, memory, SN) { this. model = model; this.screen = screen; this.memory = memory; this.SN = SN; } var phones = []; for (var i = 0; i < 1000000; i++) { var memory = i % 2 == 0 ? 16 : 32; phones.push(new Iphone("iphone6s", 5.0, memory, i)); }
這段代碼中,創(chuàng)建了一百萬(wàn)個(gè)iphone,每個(gè)iphone都獨(dú)立申請(qǐng)一個(gè)內(nèi)存。但是我們仔細(xì)觀察可以看到,大部分iphone都是類似的,只是內(nèi)存和序列號(hào)不一樣,如果是一個(gè)對(duì)性能要求比較高的程序,我們就要考慮去優(yōu)化它。
大量相似對(duì)象的程序,我們就可以考慮用享元模式去優(yōu)化它,我們分析出大部分的iphone的型號(hào),屏幕,內(nèi)存都是一樣的,那這部分?jǐn)?shù)據(jù)就可以公用,就是享元模式中的內(nèi)在數(shù)據(jù),定義享元類如下:
function IphoneFlyweight(model, screen, memory) { this.model = model; this.screen = screen; this.memory = memory; }
我們定義了iphone的享元類,其中包含型號(hào),屏幕和內(nèi)存三個(gè)數(shù)據(jù)。我們還需要一個(gè)享元工廠來(lái)維護(hù)這些數(shù)據(jù):
var flyweightFactory = (function () { var iphones = {}; return { get: function (model, screen, memory) { var key = model + screen + memory; if (!iphones[key]) { iphones[key] = new IphoneFlyweight(model, screen, memory); } return iphones[key]; } }; })();
在這個(gè)工廠中,我們定義了一個(gè)字典來(lái)保存享元對(duì)象,提供一個(gè)方法根據(jù)參數(shù)來(lái)獲取享元對(duì)象,如果字典中有則直接返回,沒(méi)有則創(chuàng)建一個(gè)返回。
接著我們創(chuàng)建一個(gè)客戶端類,這個(gè)客戶端類就是修改自iphone類:
function Iphone(model, screen, memory, SN) { this.flyweight = flyweightFactory.get(model, screen, memory); this.SN = SN; }
然后我們依舊像之間那樣生成多個(gè)iphone
var phones = []; for (var i = 0; i < 1000000; i++) { var memory = i % 2 == 0 ? 16 : 32; phones.push(new Iphone("iphone6s", 5.0, memory, i)); } console.log(phones);
這里的關(guān)鍵就在于Iphone構(gòu)造函數(shù)里面的this.flyweight = flyweightFactory.get(model, screen, memory)。這句代碼通過(guò)享元工廠去獲取享元數(shù)據(jù),而在享元工廠里面,如果已經(jīng)存在相同數(shù)據(jù)的對(duì)象則會(huì)直接返回對(duì)象,多個(gè)iphone對(duì)象共享這部分相同的數(shù)據(jù),所以原本類似的數(shù)據(jù)已經(jīng)大大減少,減少的內(nèi)存的占用。
享元模式在DOM中的應(yīng)用
享元模式的一個(gè)典型應(yīng)用就是DOM事件操作,DOM事件機(jī)制分成事件冒泡和事件捕獲。我們簡(jiǎn)單介紹一下這兩者:
事件冒泡:綁定的事件從最里層的元素開(kāi)始觸發(fā),然后冒泡到最外層
事件捕獲:綁定的事件從最外層的元素開(kāi)始觸發(fā),然后傳到最里層
假設(shè)我們HTML中有一個(gè)菜單列表
<ul class="menu"> <li class="item">選項(xiàng)1</li> <li class="item">選項(xiàng)2</li> <li class="item">選項(xiàng)3</li> <li class="item">選項(xiàng)4</li> <li class="item">選項(xiàng)5</li> <li class="item">選項(xiàng)6</li> </ul>
點(diǎn)擊菜單項(xiàng),進(jìn)行相應(yīng)的操作,我們通過(guò)jQuery來(lái)綁定事件,一般會(huì)這么做:
$(".item").on("click", function () { console.log($(this).text()); })
給每個(gè)列表項(xiàng)綁定事件,點(diǎn)擊輸出相應(yīng)的文本。這樣看暫時(shí)沒(méi)有什么問(wèn)題,但是如果是一個(gè)很長(zhǎng)的列表,尤其是在移動(dòng)端特別長(zhǎng)的列表時(shí),就會(huì)有性能問(wèn)題,因?yàn)槊總€(gè)項(xiàng)都綁定了事件,都占用了內(nèi)存。但是這些事件處理程序其實(shí)都是很類似的,我們就要對(duì)其優(yōu)化。
$(".menu").on("click", ".item", function () { console.log($(this).text()); })
通過(guò)這種方式進(jìn)行事件綁定,可以減少事件處理程序的數(shù)量,這種方式叫做事件委托,也是運(yùn)用了享元模式的原理。事件處理程序是公用的內(nèi)在部分,每個(gè)菜單項(xiàng)各自的文本就是外在部分。我們簡(jiǎn)單說(shuō)下事件委托的原理:點(diǎn)擊菜單項(xiàng),事件會(huì)從li元素冒泡到ul元素,我們綁定事件到ul上,實(shí)際上就綁定了一個(gè)事件,然后通過(guò)事件參數(shù)event里面的target來(lái)判斷點(diǎn)擊的具體是哪一個(gè)元素,比如低級(jí)第一個(gè)li元素,event.target就是li,這樣就能拿到具體的點(diǎn)擊元素了,就可以根據(jù)不同元素進(jìn)行不同的處理。
總結(jié)
享元模式是一種優(yōu)化程序性能的手段,通過(guò)共享公用數(shù)據(jù)來(lái)減少對(duì)象數(shù)量以達(dá)到優(yōu)化程序的手段。享元模式適用于擁有大量類似對(duì)象并且對(duì)性能有要求的場(chǎng)景。因?yàn)橄碓J叫枰蛛x內(nèi)部和外部數(shù)據(jù),增加了程序的邏輯復(fù)雜性,建議對(duì)性能有要求的時(shí)候才使用享元模式。
享元模式之利:
可以把網(wǎng)頁(yè)的資源符合降低幾個(gè)數(shù)量級(jí)。即使享元模式的應(yīng)用無(wú)法將實(shí)例的個(gè)數(shù)削減到一個(gè),你仍能夠從中獲益不少。
這種節(jié)省不需要大量修改原有代碼。在創(chuàng)建了管理器、工廠和享元之后,就需要對(duì)代碼進(jìn)行的修改只不過(guò)是從直接實(shí)例化目標(biāo)類改為調(diào)用管理器對(duì)象的某個(gè)方法。
享元模式之弊:
如果把它用在不必要的地方,其結(jié)果反而有損代碼的運(yùn)行效率。這種模式在優(yōu)化代碼的同時(shí),也提高了其復(fù)雜程度,這會(huì)給調(diào)試和維護(hù)造成困難。
它之所以會(huì)妨礙調(diào)試,是因?yàn)楝F(xiàn)在可能出錯(cuò)的地方變成了三個(gè):管理器、工廠和享元。
這種優(yōu)化也會(huì)使維護(hù)變得更加困難?,F(xiàn)在你面對(duì)的不是由封裝著數(shù)據(jù)的對(duì)象構(gòu)成的清晰架構(gòu),而是一堆又碎又亂的東西。其中的數(shù)據(jù)至少分兩處保存。最好注釋標(biāo)明內(nèi)在數(shù)據(jù)和外在數(shù)據(jù)。
只有在必要的時(shí)候才應(yīng)該進(jìn)行這種優(yōu)化。必須在運(yùn)行效率和可維護(hù)性之間進(jìn)行權(quán)衡。如果拿不準(zhǔn)是否需要使用享元模式,那么你很可能并不需要它。享元模式適合的是系統(tǒng)資源已經(jīng)用得差不多而且明顯需要進(jìn)行某種優(yōu)化這樣一類場(chǎng)合。
這種模式對(duì)Javascript程序員特別有用,因?yàn)樗梢杂脕?lái)減少網(wǎng)頁(yè)上所要使用的DOM元素的數(shù)量,要知道這些元素需要耗費(fèi)許多內(nèi)存。結(jié)合使用這種模式與組合模式等組織型可以開(kāi)發(fā)出功能豐富的復(fù)雜Web應(yīng)用系統(tǒng),它們可以平穩(wěn)的運(yùn)行在任何現(xiàn)代Javascript環(huán)境中。
享元模式的適用場(chǎng)合:
網(wǎng)頁(yè)中必須使用了大量資源密集型對(duì)象。如果只會(huì)用到少許這類對(duì)象,這種優(yōu)化并不劃算。
對(duì)象中所保存的數(shù)據(jù)至少有一部分能被轉(zhuǎn)化為外在數(shù)據(jù)。此外,將這些數(shù)據(jù)存儲(chǔ)在對(duì)象外部所占用的資源應(yīng)該相對(duì)較少,否則這種做法對(duì)于性能的提示實(shí)際上毫無(wú)意義。那種大量包含基礎(chǔ)性代碼和HTML內(nèi)容的對(duì)象可能比較適合這種優(yōu)化。
將外在數(shù)據(jù)分離出去后,獨(dú)一無(wú)二的對(duì)象的數(shù)目相對(duì)較少。
- javascript 設(shè)計(jì)模式之享元模式原理與應(yīng)用詳解
- JavaScript設(shè)計(jì)模式之享元模式實(shí)例詳解
- js設(shè)計(jì)模式之結(jié)構(gòu)型享元模式詳解
- 學(xué)習(xí)JavaScript設(shè)計(jì)模式之享元模式
- 深入理解JavaScript系列(37):設(shè)計(jì)模式之享元模式詳解
- javascript設(shè)計(jì)模式 – 外觀模式原理與用法實(shí)例分析
- javascript設(shè)計(jì)模式 – 裝飾模式原理與應(yīng)用實(shí)例分析
- javascript設(shè)計(jì)模式 – 組合模式原理與應(yīng)用實(shí)例分析
- javascript設(shè)計(jì)模式 – 橋接模式原理與應(yīng)用實(shí)例分析
- JS中間件設(shè)計(jì)模式的深入探討與實(shí)例分析
- javascript設(shè)計(jì)模式 – 享元模式原理與用法實(shí)例分析
相關(guān)文章
Javascript學(xué)習(xí)筆記之函數(shù)篇(五) : 構(gòu)造函數(shù)
javascript本身是沒(méi)有類的概念,只有函數(shù)的概念。javascript的類實(shí)際上也是一個(gè)javascript的函數(shù),在這個(gè)特殊的函數(shù)中間可以包含變量和其他javascript函數(shù)的引用。那么這個(gè)特殊的函數(shù)本身就是javascript所謂類的構(gòu)造函數(shù)。2014-11-11JavaScript mapreduce工作原理簡(jiǎn)析
MapReduce是一個(gè)編程模型,用于作業(yè)調(diào)度,也是一個(gè)處理和生成超大數(shù)據(jù)集的算法模型的相關(guān)實(shí)現(xiàn),本文將詳細(xì)介紹JavaScript mapreduce工作原理,需要的朋友可以參考下2012-11-11JavaScript中Function()函數(shù)的使用教程
這篇文章主要介紹了JavaScipt中Function()函數(shù)的使用教程,是JavaScipt入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-06-06Javascript學(xué)習(xí)筆記2 函數(shù)
在Javascript中,function才是Javascript的第一型。當(dāng)我們寫(xiě)下一段函數(shù)時(shí),其實(shí)不過(guò)是建立了一個(gè)function類型的實(shí)體。2010-01-015個(gè)最頂級(jí)jQuery圖表類庫(kù)插件【jquery插件庫(kù)】
這篇文章主要介紹了5個(gè)最頂級(jí)jQuery圖表類庫(kù)插件【jquery插件庫(kù)】,需要的朋友可以參考下2016-05-05Javascript基礎(chǔ)教程之函數(shù)對(duì)象和屬性
這篇文章主要介紹了Javascript基礎(chǔ)教程之函數(shù)對(duì)象和屬性,需要的朋友可以參考下2015-01-01