享元模式
在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