享元模式(Flyweight),運(yùn)行共享技術(shù)有效地支持大量細(xì)粒度的對(duì)象,避免大量擁有相同內(nèi)容的小類的開銷(如耗費(fèi)內(nèi)存),使大家共享一個(gè)類(元類)。
享元模式可以避免大量非常相似類的開銷,在程序設(shè)計(jì)中,有時(shí)需要生產(chǎn)大量細(xì)粒度的類實(shí)例來(lái)表示數(shù)據(jù),如果能發(fā)現(xiàn)這些實(shí)例除了幾個(gè)參數(shù)以外,開銷基本相同的 話,就可以大幅度較少需要實(shí)例化的類的數(shù)量。如果能把那些參數(shù)移動(dòng)到類實(shí)例的外面,在方法調(diào)用的時(shí)候?qū)⑺麄儌鬟f進(jìn)來(lái),就可以通過(guò)共享大幅度第減少單個(gè)實(shí)例 的數(shù)目。
那么如果在JavaScript中應(yīng)用享元模式呢?有兩種方式,第一種是應(yīng)用在數(shù)據(jù)層上,主要是應(yīng)用在內(nèi)存里大量相似的對(duì)象上;第二種是應(yīng)用在DOM層上,享元可以用在中央事件管理器上用來(lái)避免給父容器里的每個(gè)子元素都附加事件句柄。
Flyweight中有兩個(gè)重要概念--內(nèi)部狀態(tài)intrinsic和外部狀態(tài)extrinsic之分,內(nèi)部狀態(tài)就是在對(duì)象里通過(guò)內(nèi)部方法管理,而外部信息可以在通過(guò)外部刪除或者保存。
說(shuō)白點(diǎn),就是先捏一個(gè)的原始模型,然后隨著不同場(chǎng)合和環(huán)境,再產(chǎn)生各具特征的具體模型,很顯然,在這里需要產(chǎn)生不同的新對(duì)象,所以Flyweight模式中常出現(xiàn)Factory模式,F(xiàn)lyweight的內(nèi)部狀態(tài)是用來(lái)共享的,F(xiàn)lyweight factory負(fù)責(zé)維護(hù)一個(gè)Flyweight pool(模式池)來(lái)存放內(nèi)部狀態(tài)的對(duì)象。
讓我們來(lái)演示一下如果通過(guò)一個(gè)類庫(kù)讓系統(tǒng)來(lái)管理所有的書籍,每個(gè)書籍的元數(shù)據(jù)暫定為如下內(nèi)容:
ID Title Author Genre Page count Publisher ID ISBN
我們還需要定義每本書被借出去的時(shí)間和借書人,以及退書日期和是否可用狀態(tài):
checkoutDate checkoutMember dueReturnDate availability
因?yàn)閎ook對(duì)象設(shè)置成如下代碼,注意該代碼還未被優(yōu)化:
var Book = function( id, title, author, genre, pageCount,publisherID, ISBN, checkoutDate, checkoutMember, dueReturnDate,availability ){ this.id = id; this.title = title; this.author = author; this.genre = genre; this.pageCount = pageCount; this.publisherID = publisherID; this.ISBN = ISBN; this.checkoutDate = checkoutDate; this.checkoutMember = checkoutMember; this.dueReturnDate = dueReturnDate; this.availability = availability; }; Book.prototype = { getTitle:function(){ return this.title; }, getAuthor: function(){ return this.author; }, getISBN: function(){ return this.ISBN; }, /其它get方法在這里就不顯示了/ // 更新借出狀態(tài) updateCheckoutStatus: function(bookID, newStatus, checkoutDate,checkoutMember, newReturnDate){ this.id = bookID; this.availability = newStatus; this.checkoutDate = checkoutDate; this.checkoutMember = checkoutMember; this.dueReturnDate = newReturnDate; }, //續(xù)借 extendCheckoutPeriod: function(bookID, newReturnDate){ this.id = bookID; this.dueReturnDate = newReturnDate; }, //是否到期 isPastDue: function(bookID){ var currentDate = new Date(); return currentDate.getTime() > Date.parse(this.dueReturnDate); } };
程序剛開始可能沒問(wèn)題,但是隨著時(shí)間的增加,圖書可能大批量增加,并且每種圖書都有不同的版本和數(shù)量,你將會(huì)發(fā)現(xiàn)系統(tǒng)變得越來(lái)越慢。幾千個(gè)book對(duì)象在內(nèi)存里可想而知,我們需要用享元模式來(lái)優(yōu)化。
我們可以將數(shù)據(jù)分成內(nèi)部和外部?jī)煞N數(shù)據(jù),和book對(duì)象相關(guān)的數(shù)據(jù)(title, author 等)可以歸結(jié)為內(nèi)部屬性,而(checkoutMember, dueReturnDate等)可以歸結(jié)為外部屬性。這樣,如下代碼就可以在同一本書里共享同一個(gè)對(duì)象了,因?yàn)椴还苷l(shuí)借的書,只要書是同一本書,基本信息是一樣的:
/享元模式優(yōu)化代碼/ var Book = function(title, author, genre, pageCount, publisherID, ISBN){ this.title = title; this.author = author; this.genre = genre; this.pageCount = pageCount; this.publisherID = publisherID; this.ISBN = ISBN; };
讓我們來(lái)定義一個(gè)基本工廠,用來(lái)檢查之前是否創(chuàng)建該book的對(duì)象,如果有就返回,沒有就重新創(chuàng)建并存儲(chǔ)以便后面可以繼續(xù)訪問(wèn),這確保我們?yōu)槊恳环N書只創(chuàng)建一個(gè)對(duì)象:
/ Book工廠 單例 / var BookFactory = (function(){ var existingBooks = {}; return{ createBook: function(title, author, genre,pageCount,publisherID,ISBN){ /查找之前是否創(chuàng)建/ var existingBook = existingBooks[ISBN]; if(existingBook){ return existingBook; }else{ / 如果沒有,就創(chuàng)建一個(gè),然后保存/ var book = new Book(title, author, genre,pageCount,publisherID,ISBN); existingBooks[ISBN] = book; return book; } } } });
外部狀態(tài),相對(duì)就簡(jiǎn)單了,除了我們封裝好的book,其它都需要在這里管理:
/BookRecordManager 借書管理類 單例/ var BookRecordManager = (function(){ var bookRecordDatabase = {}; return{ /添加借書記錄/ addBookRecord: function(id, title, author, genre,pageCount,publisherID,ISBN, checkoutDate, checkoutMember, dueReturnDate, availability){ var book = bookFactory.createBook(title, author, genre,pageCount,publisherID,ISBN); bookRecordDatabase[id] ={ checkoutMember: checkoutMember, checkoutDate: checkoutDate, dueReturnDate: dueReturnDate, availability: availability, book: book; }; }, updateCheckoutStatus: function(bookID, newStatus, checkoutDate, checkoutMember, newReturnDate){ var record = bookRecordDatabase[bookID]; record.availability = newStatus; record.checkoutDate = checkoutDate; record.checkoutMember = checkoutMember; record.dueReturnDate = newReturnDate; }, extendCheckoutPeriod: function(bookID, newReturnDate){ bookRecordDatabase[bookID].dueReturnDate = newReturnDate; }, isPastDue: function(bookID){ var currentDate = new Date(); return currentDate.getTime() > Date.parse(bookRecordDatabase[bookID].dueReturnDate); } }; });
通過(guò)這種方式,我們做到了將同一種圖書的相同信息保存在一個(gè)bookmanager對(duì)象里,而且只保存一份;相比之前的代碼,就可以發(fā)現(xiàn)節(jié)約了很多內(nèi)存。
關(guān)于DOM的事件冒泡,在這里就不多說(shuō)了,相信大家都已經(jīng)知道了,我們舉兩個(gè)例子。
舉例來(lái)說(shuō),如果我們又很多相似類型的元素或者結(jié)構(gòu)(比如菜單,或者ul里的多個(gè)li)都需要監(jiān)控他的click事件的話,那就需要多每個(gè)元素進(jìn)行事件綁定,如果元素有非常非常多,那性能就可想而知了,而結(jié)合冒泡的知識(shí),任何一個(gè)子元素有事件觸發(fā)的話,那觸發(fā)以后事件將冒泡到上一級(jí)元素,所以利用這個(gè)特性,我們可以使用享元模式,我們可以對(duì)這些相似元素的父級(jí)元素進(jìn)行事件監(jiān)控,然后再判斷里面哪個(gè)子元素有事件觸發(fā)了,再進(jìn)行進(jìn)一步的操作。
在這里我們結(jié)合一下jQuery的bind/unbind方法來(lái)舉例。
HTML:
<div id="container"> <div class="toggle" href="#">更多信息 (地址) <span class="info"> 這里是更多信息 </span></div> <div class="toggle" href="#">更多信息 (地圖) <span class="info"> <iframe src="http://www.map-generator.net/extmap.php?name=London&address=london%2C%20england&width=500...gt;"</iframe> </span> </div> </div>
JavaScript:
stateManager = { fly: function(){ var self = this; $('#container').unbind().bind("click", function(e){ var target = $(e.originalTarget || e.srcElement); // 判斷是哪一個(gè)子元素 if(target.is("div.toggle")){ self.handleClick(target); } }); }, handleClick: function(elem){ elem.find('span').toggle('slow'); } });
另外一個(gè)例子,依然和jQuery有關(guān),一般我們?cè)谑录幕卣{(diào)函數(shù)里使用元素對(duì)象是會(huì)后,經(jīng)常會(huì)用到$(this)這種形式,其實(shí)它重復(fù)創(chuàng)建了新對(duì)象,因?yàn)楸旧砘卣{(diào)函數(shù)里的this已經(jīng)是DOM元素自身了,我們必要必要使用如下這樣的代碼:
$('div').bind('click', function(){ console.log('You clicked: ' + $(this).attr('id')); }); // 上面的代碼,要避免使用,避免再次對(duì)DOM元素進(jìn)行生成jQuery對(duì)象,因?yàn)檫@里可以直接使用DOM元素自身了。 $('div').bind('click', function(){ console.log('You clicked: ' + this.id); });
其實(shí),如果非要用$(this)這樣的形式,我們也可以實(shí)現(xiàn)自己版本的單實(shí)例模式,比如我們來(lái)實(shí)現(xiàn)一個(gè)jQuery.signle(this)這樣的函數(shù)以便返回DOM元素自身:
jQuery.single = (function(o){ var collection = jQuery([1]); return function(element) { // 將元素放到集合里 collection[0] = element; // 返回集合 return collection; }; });
使用方法:
$('div').bind('click', function(){ var html = jQuery.single(this).next().html(); console.log(html); });
這樣,就是原樣返回DOM元素自身了,而且不進(jìn)行jQuery對(duì)象的創(chuàng)建。
Flyweight模式是一個(gè)提高程序效率和性能的模式,會(huì)大大加快程序的運(yùn)行速度.應(yīng)用場(chǎng)合很多:比如你要從一個(gè)數(shù)據(jù)庫(kù)中讀取一系列字符串,這些字符串中有許多是重復(fù)的,那么我們可以將這些字符串儲(chǔ)存在Flyweight池(pool)中。
如果一個(gè)應(yīng)用程序使用了大量的對(duì)象,而這些大量的對(duì)象造成了很大的存儲(chǔ)開心時(shí)就應(yīng)該考慮使用享元模式;還有就是對(duì)象的大多數(shù)狀態(tài)可以外部狀態(tài),如果刪除對(duì)象的外部狀態(tài),那么就可以用相對(duì)較少的共享對(duì)象取代很多組對(duì)象,此時(shí)可以考慮使用享元模式。
參考地址:http://www.addyosmani.com/resources/essentialjsdesignpatterns/book/#detailflyweight
更多建議: