在JavaScript中,瀏覽器特別是移動端的瀏覽器分配的記憶體很有限,如何節省記憶體就成了一件非常有意義的事情。節省記憶體的一個有效方法是減少對象的數量。
享元模式(Flyweight),運行共用技術有效地支援大量細粒度的對象,避免大量擁有相同內容的小類的開銷(如耗費記憶體),使大家共用一個類(元類)。
享元模式可以避免大量非常相似類的開銷,在程式設計中,有時需要生產大量細粒度的類執行個體來表示資料,如果能發現這些執行個體除了幾個參數以外,開銷基本相同的話,就可以大幅度較少需要執行個體化的類的數量。如果能把那些參數移動到類執行個體的外面,在方法調用的時候將他們傳遞進來,就可以通過共用大幅度第減少單個執行個體 的數目。
在JavaScript中應用享元模式有兩種方式,第一種是應用在資料層上,主要是應用在記憶體裡大量相似的對象上;第二種是應用在DOM層上,享元可以用在中央事件管理器上用來避免給父容器裡的每個子項目都附加事件控制代碼
Flyweight中有兩個重要概念--內部狀態intrinsic和外部狀態extrinsic之分,內部狀態就是在對象裡通過內部方法管理,而外部資訊可以在通過外部刪除或者儲存。
說白點,就是先捏一個的原始模型,然後隨著不同場合和環境,再產生各具特徵的具體模型,很顯然,在這裡需要產生不同的新對象,所以Flyweight模式中常出現Factory模式,Flyweight的內部狀態是用來共用的,Flyweight factory負責維護一個Flyweight pool(模式池)來存放內部狀態的對象。
我們可以將內部狀態相同的所有對象替換為同一個共用對象,而要建立這樣一個共用對象就需要用到單例Factory 方法,而不是普通的建構函式,這樣做可以跟蹤到已經執行個體化的各個對象,從而僅當所需對象的內部狀態不同於已有對象時才建立一個新對象。對象的外在狀態被儲存在一個管理器對象中。在調用對象的方法時,管理器會把這些外在狀態作為參數傳入。
把一個對象的資料儲存在兩個不同的對象中(共用對象、管理器對象)
1.共用對象(享元對象)
2.單例Factory 方法(建立共用對象)
3.管理器對象(管理外部狀態)
比如圖書館中的一本書可以用一個對象來表示,他有很多屬性
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設計模式與開發實踐》
以上就是本文的全部內容,希望對大家的學習有所協助,也希望大家多多支援雲棲社區。