var id = 0;
window.startUpload = function (uploadType, files) {
for (var i = 0, file; file = files[i++];) {
var uploadObj = new Upload(uploadType, file.fileName, file.fileSize);
uploadObj.init(id++); // 给upload对象设置一个唯一的id
}
}
var uploadManager = (function() {
var uploadDatabase = {};
return {
add: function (id, uploadType, fileName, fileSize) {
var flyWeightObj = UploadFactory.create(uploadType);
var dom = docuemnt.createElement('div');
dom.innerHTML = '<span>文件名:' + fileName + ',文件大小:' + fileSize + '</span><button class="delFile">删除</button>'
dom.querySelector('.delFile').onclick = function () {
flyWeightObj.delFile(id);
}
document.body.appendChild(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];
}
}
}
})();
然后是开始触发上传动作的startUpload函数
var id = 0;
window.startUplaod = function (uploadTypes, files) {
for (var i = 0, file; file = [files++];) {
var uploadObj = uploadManager.add(++id, uploadType, file.fileName, file.fileSize);
}
}
享元模式
享元模式(flyweight)是一种用于性能优化的模式,享元模式的核心是运用共享技术来有效支持大量细粒度的对象。 如果系统中因为创建了大量类似的对象而导致内存占用过高,就可以考虑使用享元模式了
享元模式的目标是尽量减少共享对象的数量,将对象的属性划分为内部状态与外部状态(状态在这里通常指属性)。关于如何划分内部状态和外部状态,下面有几条指引
这样一来,我们便可以把所有内部状态相同的对象都指定为同一个共享的对象。而外部状态可以从对象身上剥离出来,并储存在外部。 剥离了外部状态的对象成为共享对象,外部状态在必要时被传入共享对象来组装成一个完整的对象。虽然组装外部状态成为一个完整对象的过程需要花费一定的时间,但却可以大大减少系统中的对象数量,相比之下,这点时间或许是微不足道的。因此,享元模式是一种用时间换空间的优化模式。
使用享元模式的关键是如何区别内部状态和外部状态。可以被对象共享的属性通常被划分为内部状态,而外部状态取决于具体的场景,并根据场景而变化。
案例
首先我们看一个文件上传的例子
当用户选择完文件之后,
startUpload
函数会遍历files数组来创建对应的upload对象。下面来看看Upload
构造函数,它接受3个参数,分别是插件类型、文件名和文件大小。这些信息都已经被插件组装在files数组里返回为了简化示例,只保留了
delFile
方法,该方法是判断当被删除的文件小于3000kb时,直接删除,否则弹出提示框接下来创建3个插件上传对象和3个Flash上传对象
可以看出来,我们上传了几个文件,那么浏览器中就有多少个upload对象。当我们上传文件足够多的时候,就会容易出现内存崩溃的状态
使用享元模式进行重构
重构时首先我们需要确定什么是upload的内部状态,什么是外部状态,根据划分内外状态的关键点,可以判断出
uploadType
是对象的内部状态,因为upload对象必须依赖uploadType属性才能工作。剥离外部状态
明确了
uploadType
作为内部状态之后,我们再把其他的外部状态从构造函数中抽离出来Upload.prototype.init
函数也不再需要,因为upload对象初始化的工作放在了uploadManager.add函数里面,接下来只需要定义Upload.prototype.del
函数即可因为我们upload内部没有大小,所以在删除之前,需要读取外部管理器中文件的大小。
工厂进行对象实例化
接下来定义一个工厂来创建
upload
对象,如果某种内部状态对应的共享对象已经被创建国,那么直接返回这个对象,否则创建一个新的对象:管理器封装外部状态
现在我们来完善前面提到的
uploadManager
对象,它负责向UploadFactory
提交创建对象的请求,并用一个uploadDatabase
对象保存所有upload
对象的外部状态,以便在程序运行过程中给upload
共享对象设置外部状态:然后是开始触发上传动作的
startUpload
函数最后是测试时间
重构之前的代码我们创建了六个对象。而通过享元模式重构之后,对象的数量减少为2,而且对于同一种uploadType,不论增加多少个文件,始终都是一个对象。
鸣谢
本文copy于曾探大佬的《JavaScript设计模式与开发实践》