享元模式
内部状态与外部状态
内部状态存储于对象内部。
内部状态可以被一些对象共享。
内部状态独立于具体的场景,通常不会改变。
外部状态取决于具体的场景,并根据场景而变化,外部状态不能被共享。
内部状态与外部状态分离
例子:通过plugin或flash两种方式上传文件l
首先会获得需要的特定内部状态的对象,如果已在createdFlyWeightObjs对象属性中,则直接返回,否则new Upload,并放入createdFlyWeightObjs对象中已备用,之后再设置目标对象的外部状态就生成了想要的对象
- 剥离外部状态
明确了 uploadType 作为内部状态之后,我们再把其他的外部状态从构造函数中抽离出来,Upload 构造函数中只保留 uploadType 参数:
var Upload = function (uploadType) {
this.uploadType = uploadType;
};
Upload.prototype.delFile = function (id) {
uploadManager.setExternalState(id, this); // (1)
if (this.fileSize < 3000) {
return this.dom.parentNode.removeChild(this.dom);
}
if (window.confirm('确定要删除该文件吗? ' + this.fileName)) {
return this.dom.parentNode.removeChild(this.dom);
}
};
在开始删除文件之前,需要读取文件的实际大小,而文件的实际大小被储存在外部管理器uploadManager 中,所以在这里需要通uploadManager.setExternalState 方法给共享对象设置正确的 fileSize ,上段代码中的(1)处表示把当前 id 对应的对象的外部状态都组装到共享对象中。
- 工厂进行对象实例化(内部状态)
接下来定义一个工厂来创建 upload 对象,如果某种内部状态对应的共享对象已经被创建过,那么直接返回这个对象,否则创建一个新的对象:
var UploadFactory = (function () {
var createdFlyWeightObjs = {};
return {
create: function (uploadType) {
if (createdFlyWeightObjs[uploadType]) {
return createdFlyWeightObjs[uploadType];
}
return createdFlyWeightObjs[uploadType] = new Upload(uploadType);
}
}
})();
- 管理器封装外部状态(外部状态)
现在我们来完善前面提到的 uploadManager 对象,它负责向 UploadFactory 提交创建对象的请求,并用一个 uploadDatabase 对象保存所有 upload 对象的外部状态,以便在程序运行过程中给upload 共享对象设置外部状态,代码如下:
var uploadManager = (function () {
var uploadDatabase = {};
return {
add: function (id, uploadType, fileName, fileSize) {
var flyWeightObj = UploadFactory.create(uploadType);
var dom = document.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.startUpload = function (uploadType, files) {
for (var i = 0, file; file = files[i++];) {
var uploadObj = uploadManager.add(++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
}
]);
对象池
对象池,获取对象时先检查对象池里面有没有所需对象,如果有直接返回一个,没有则创建一个。
我们还可以在对象池工厂里,把创建对象的具体过程封装起来,实现一个通用的对象池:
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:// baidu.com';
var iframe2 = iframeFactory.create();
iframe2.src = 'http:// QQ.com';
setTimeout(function () {
var iframe3 = iframeFactory.create();
iframe3.src = 'http:// 163.com';
}, 3000);
对象池是另外一种性能优化方案,它跟享元模式有一些相似之处,但没有分离内部状态和外部状态这个过程。