设计模式(十一):享元模式
享元(Flyweight)模式是一种经典的结构型设计模式,用于优化重复、缓慢及数据共享效率较低的代码。它旨在通过与相关对象共享尽可能多的数据来减少应用程序中内存的使用。
应用场景
(1)用于数据层,处理内存中保存的大量相似对象的共享数据。
(2)用于DOM层,Flyweight用作中央事件管理器,来避免将事件处理程序附加到父容器中的每个子元素上,而是将事件处理程序附加到这个父容器上。
示例
(1)用于数据层。管理图书馆中的所有书籍,享元优化。
// 每种书的数据,作为共享数据。 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; }; // 书籍单例,获取共享数据(书籍)的方法。 var BookFactory = (function () { var existingBooks = {}, existingBook; return { createBook: function ( title, author, genre, pageCount, publisherID, ISBN ) { existingBook = existingBooks[ISBN]; if ( !!existingBook ) { return existingBook; } else { var book = new Book( title, author, genre, pageCount, publisherID, ISBN ); existingBooks[ISBN] = book; return book; } } }; })(); //书籍记录管理器单例 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 ); } }; })();
每本书除了作为共享数据的书籍信息(内部数据)外,还有额外借出人、借出日期、归还日期等外部数据,完全相同的书可以共享书籍信息。
(2)用于DOM层
HTML:
<div id="container"> <div class="toggle" href="#">More Info (Address) <span class="info"> This is more information </span></div> <div class="toggle" href="#">Even More Info (Map) <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>
JS:
var stateManager = { fly: function () { var self = this; $( "#container" ) .unbind() .on( "click", "div.toggle", function ( e ) { self.handleClick( e.target ); }); }, handleClick: function ( elem ) { elem.find( "span" ).toggle( "slow" ); } };
通常在构建我们自己的菜单、accordion组件或其他基于列表的小部件时,我们要做的就是将一个点击事件绑定至父容器中的每个链接元素上。
其实不需将点击绑定至多个元素,我们可以将享元附加到容器的顶部,它可以监听来自下面的事件。这样的优点是,可以将很多独立的动作转变成一个共享的动作(可能会节省内存)。
总结
可能在写前端代码的时候,大家(包括我)都不会大注意内存的使用,有时的确存在冗余对象,但因为影响不大,就不大注意。不过既然知道好的写法是怎么样,我觉得可以这样写,养成一个好习惯。
参考文献
1. 《JavaScript设计模式》by 徐涛【译】
本文为原创文章,转载请保留原出处,方便溯源,如有错误地方,谢谢指正。