JavaScript享元模式
享元(flyweight----蝇量级)模式是一种用于性能优化的模式。享元模式的核心是运用共享技术来有效支持大量细粒度的对象。如果系统中因为创建了大量类似的对象而导致内存占用过高,享元模式就很有用了。在JavaScript中,浏览器特别是移动端的浏览器分配的内存并不算多,如何节省内存就成了一件非常有意义的事。
享元模式要求将对象的属性划分为内部状态与外部状态(在这里状态是指属性)。享元模式的目标是尽量减少共享对象的数量。如何划分内部状态和外部状态,可以参考一下经验:
1.内部状态存储于对象内部
2.内部状态可以被一些对象共享
3.内部状态独立于具体的场景,通常不会改变
4.外部状态取决于具体的场景,并根据场景而变化,外部状态不能被共享
使用享元模式的关键就是区别内部状态和外部状态。
使用了享元模式后,会因为要维护一些多出来的对象,造成额外的开销。但它又有它的好处。享元模式带来的好处很大程度上取决于如何使用及何时使用,一般来说,一下情况发生时便可以使用享元模式:
1.一个程序中使用了大量的相似对象
2.由于使用了大量对象,造成很大的内存开销
3.对象的大多数状态都可以变为外部状态
4.剥离出对象的外部状态后,可以用相对较少的共享对象取代大量对象
实例:微云上传文件
微云支持好几种上传方式,比如浏览器插件、Flash和表单上传等,为了简化例子,假设只有插件和Flash这两种。不论是插件上传,还是Flash上传,原理都是一样的,当用户选择了文件之后,插件和Flash都会通知调用window下的一个全局JavaScript函数startUpload,用户选择的文件列表被组合成一个数组files塞进该函数的参数列表里:
var id = 0;
window.startUpload = function(uploadTypd, files){ //uploadType 区分是控件还是flash
for(var i=0, file; file = files[ i++]; ){
var uploadObj = new Upload( uploadType, file.fileName, file.fileSize );
uploadObj.init( id++ ); //给upload对象设置一个唯一的id
}
}
var Upload = function( uploadType, fileName, fileSize ){
this.uploadType = uploadType;
this.fileName = fileName;
this.fileSize = fileSize;
this.dom = null;
}
Upload.prototype.init = function(id){
var that = this;
this.id = id;
this.dom = document.createElement("div");
this.dom.innerHTML = '<span>文件名称:' + this.fileName + ', 文件大小:' + this.fileSize + '</span>' +
'<button class="delFile">删除</button>';
this.dom.querySelector('.delFile').onclick = function(){
that.delFile();
}
document.body.appendChild( this.dom );
};
Upload.prototype.delFile = function(){
if( this.fileSize < 3000 ){
return this.dom.parentNode.removeChild( this.dom );
}
if( window.confirm( '确定要删除该文件吗?' + this.fileName ) ){
return this.dom.parentNode.removeChild( this.dom );
}
};
接下来分别创建3个插件上传对象和3个Flash上传对象:
startUpload( 'plugin', [
{
fileName: '1.txt',
fileSize: 1000
}, {
fileName: '2.html',
fileSize: 3000
}, {
fileName: '3.txt',
fileSize: 5000
}
]);
startUpload( 'flash', [
{
fileName: '4.txt',
fileSize: 1000
}, {
fileName: '5.html',
fileSize: 3000
}, {
fileName: '6.txt',
fileSize: 5000
}
]);
我们需要确认插件类型uploadType是内部状态,其它则是外部状态,这样的话上面的代码可以重构,如下:
var Upload = function( uploadType ){
this.uploadType = uploadType;
};
Upload.prototype.delFile = function( id ){
uploadManager.setExternalState( id, this ); //把当前ID对应的对象的外部状态都组装到共享对象中
if( this.fileSize < 3000 ){
return this.dom.parentNode.removeChild( this.dom );
}
if( window.confirm( '确定要删除该文件吗?' + this.fileName ) ){
return this.dom.parentNode.removeChild( this.dom );
}
};
var UploadFactory = (function(){
var createdFlyWeightObjs = {};
return {
create: function(uploadType){
if( createdFlyWeightObjs[uploadType] ){
return createdFlyWeightObjs[uploadType];
}
return createdFlyWeightObjs[uploadType] = new Upload( uploadType );
}
};
})();
var uploadManager = (function(){
var uploadDatabase = {};
return {
add: function( id, uploadTypd, fileName, fileSize){
var flyWeightObj = UploadFactory.create( uploadType );
var dom = document.createElement( 'div' );
dom.innerHTML = '<span>文件名称:' + this.fileName + ', 文件大小:' + this.fileSize + '</span>' +
'<button class="delFile">删除</button>';
dom.querySelector('.delFile').onclick = function(){
flyWeightObj.delFile(id);
}
document.body.appendChild( this.dom );
uploadDatabase[ id ] = {
fileName: fileName,
fileSize: fileSize,
dom: dom
};
return flyWeightObj;
},
setExternalState: function( id, flyWeightObj ){
var uploadData = uploadDatabase[ id ];
for( var i in uploadData ){
flyWeightObj[ i ] = uploadData[ i ];
}
}
};
})();
var id = 0;
window.startUpload = function( uploadTypd, files ){
for(var i=0, file; file = files[ i++]; ){
var uploadObj = new Upload( ++id, uploadType, file.fileName, file.fileSize );
}
}
startUpload( 'plugin', [
{
fileName: '1.txt',
fileSize: 1000
}, {
fileName: '2.html',
fileSize: 3000
}, {
fileName: '3.txt',
fileSize: 5000
}
]);
startUpload( 'flash', [
{
fileName: '4.txt',
fileSize: 1000
}, {
fileName: '5.html',
fileSize: 3000
}, {
fileName: '6.txt',
fileSize: 5000
}
]);
享元模式重构前的代码里一共创建了6个upload对象,重构后,对象的数量减少为2,而且就算现在同时上传2000个文件,维持的对象也只是两个。
享元模式的关键就是把内部状态和外部状态分离开来。有多少种内部状态的组合,系统中变最多存在多少个共享对象,而外部状态存储在共享对象的外部,在必要时被传入共享对象来组装成一个完整的对象。如果没有内部状态,那么共享对象就是一个单例。如果没有外部状态,像字符串的对象池,就不完全是享元模式。