享元模式 在JavaScript中,瀏覽器特別是移動端的瀏覽器分配的記憶體很有限,如何節省記憶體就成了一件非常有意義的事情。節省記憶體的一個有效方法是減少對象的數量。 享元模式(Flyweight),運行共用技術有效地支持大量細粒度的對象,避免大量擁有相同內容的小類的開銷(如耗費記憶體),使大家共用一個類( ...
享元模式
在JavaScript中,瀏覽器特別是移動端的瀏覽器分配的記憶體很有限,如何節省記憶體就成了一件非常有意義的事情。節省記憶體的一個有效方法是減少對象的數量。
享元模式(Flyweight),運行共用技術有效地支持大量細粒度的對象,避免大量擁有相同內容的小類的開銷(如耗費記憶體),使大家共用一個類(元類)。
享元模式可以避免大量非常相似類的開銷,在程式設計中,有時需要生產大量細粒度的類實例來表示數據,如果能發現這些實例除了幾個參數以外,開銷基本相同的話,就可以大幅度較少需要實例化的類的數量。如果能把那些參數移動到類實例的外面,在方法調用的時候將他們傳遞進來,就可以通過共用大幅度第減少單個實例 的數目。
在JavaScript中應用享元模式有兩種方式,第一種是應用在數據層上,主要是應用在記憶體里大量相似的對象上;第二種是應用在DOM層上,享元可以用在中央事件管理器上用來避免給父容器里的每個子元素都附加事件句柄
Flyweight中有兩個重要概念--內部狀態intrinsic和外部狀態extrinsic之分,內部狀態就是在對象里通過內部方法管理,而外部信息可以在通過外部刪除或者保存。
說白點,就是先捏一個的原始模型,然後隨著不同場合和環境,再產生各具特征的具體模型,很顯然,在這裡需要產生不同的新對象,所以Flyweight模式中常出現Factory模式,Flyweight的內部狀態是用來共用的,Flyweight factory負責維護一個Flyweight pool(模式池)來存放內部狀態的對象。
我們可以將內部狀態相同的所有對象替換為同一個共用對象,而要創建這樣一個共用對象就需要用到單例工廠方法,而不是普通的構造函數,這樣做可以跟蹤到已經實例化的各個對象,從而僅當所需對象的內部狀態不同於已有對象時才創建一個新對象。對象的外在狀態被保存在一個管理器對象中。在調用對象的方法時,管理器會把這些外在狀態作為參數傳入。
把一個對象的數據保存在兩個不同的對象中(共用對象、管理器對象)
- 共用對象(享元對象)
- 單例工廠方法(創建共用對象)
- 管理器對象(管理外部狀態)
比如圖書館中的一本書可以用一個對象來表示,他有很多屬性
var Book = function( id, title, author, genre, pageCount,publisherID, ISBN, checkoutDate, checkoutMember, dueReturnDate,availability ){
...//初始化代碼
}
Book.prototype = {
getTitle:function(){
return this.title;
},
...
// 更新借出狀態方法
updateCheckoutStatus:function(bookID, newStatus, checkoutDate,checkoutMember, newReturnDate){...},
//續借
extendCheckoutPeriod: function(bookID, newReturnDate){...},
//是否到期
isPastDue: function(bookID){...}
}
程式剛開始可能沒問題,但是隨著時間的增加,圖書可能大批量增加,並且每種圖書都有不同的版本和數量,你將會發現系統變得越來越慢。幾千個book對象在記憶體里可想而知,我們需要用享元模式來優化。
我們可以將數據分成內部和外部兩種數據,同一本書中,和book對象相關的數據(title,author等)可以歸結為內部屬性,而(checkoutMember,dueReturnDate等)可以歸結為外部屬性。這樣,如下代碼就可以在同一本書里共用同一個對象了,因為不管誰借的書,只要書是同一本書,基本信息是一樣的:
//共用對象
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;
};
讓我們來定義一個基本工廠,用來檢查之前是否創建該book的對象,如果有就返回,沒有就重新創建並存儲以便後面可以繼續訪問,這確保我們為每一種書只創建一個對象:
/* Book工廠 單例 */
var BookFactory = (function(){
var existingBooks = {};
return{
createBook: function(title, author, genre,pageCount,publisherID,ISBN){
/*查找之前是否創建*/
var existingBook = existingBooks[ISBN];
if(existingBook){
return existingBook;
}else{
/* 如果沒有,就創建一個,然後保存*/
var book = new Book(title, author, genre,pageCount,publisherID,ISBN);
existingBooks[ISBN] = book;
return book;
}
}
}
});
外部狀態,相對就簡單了,除了我們封裝好的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);
}
};
});
通過這種方式,我們做到了將同一種圖書的相同信息保存在一個bookmanager對象里,而且只保存一份;相比之前的代碼,就可以發現節約了很多記憶體。
對象池
對象池是另外一種性能優化方案,和享元模式有一些相似之處,但沒有分離內部狀態和外部狀態這個過程。
通用對象池實現:
var objectPoolFactory = function (createObjFn) {
var objectPool = []; //對象池
return {
create: function () { //取出
var obj = objectPool.length === 0 ? createObjFn.apply(this,arguments) : objectPool.shift();
return obj;
},
recover: function (obj) { //收回
objectPool.push(obj);
}
}
};
現在利用objectPoolFactory來創建一個裝載一些iframe的對象池:
var iframeFactory = objectPoolFactory(function () {
var iframe = document.createElement('iframe');
document.body.appendChild(iframe);
iframe.onload = function () {
iframe.onload = null; //防止iframe重覆載入的bug
iframeFactory.recover(iframe); //iframe載入完成後往對象池填回節點(收回)
};
return iframe;
});
//調用
var iframe1 = iframeFactory.create();
iframe1.src = 'http://www.qq.com';
參考文獻: 《JavaScript模式》 《JavaScript設計模式與開發實踐》
http://www.cnblogs.com/TomXu/archive/2012/04/09/2379774.html