[TOC]
# 享元模式
享元(flyweight)模式是一種用于性能優化的模式,“fly”在這里是蒼蠅的意思,意為蠅量級。享元模式的核心是運用共享技術來有效支持大量細粒度的對象。
享元模式是為解決性能而生的模式,在一個存在大量相似對象的系統中,享元模式可以很好地解決大量對象帶來的性能問題。
## 內部狀態與外部狀態
享元模式要求將對象的屬性劃分為內部狀態與外部狀態(狀態在這里通常指屬性)。
- 內部狀態存儲于對象內部。
- 內部狀態可以被一些對象共享。
- 內部狀態獨立于具體的場景,通常不會改變。
- 外部狀態取決于具體的場景,并根據場景而變化,外部狀態不能被共享。
這樣一來便可以把所有內部狀態相同的對象都指定為同一個共享的對象。而外部狀態可以從對象身上剝離出來,并儲存在外部。
剝離了外部狀態的對象成為共享對象,外部狀態在必要時被傳入共享對象來組裝成一個完整的對象。
可以被對象共享的屬性通常被劃分為內部狀態,如同不管什么樣式的衣服,都可以按照性別不同,穿在同一個男模或女模身上,模特的性別就可以作為內部狀態存儲在共享對象的內部。而外部狀態取決于具體的場景,并根據場景而變化,它們不能被一些對象共享,因此只能劃分為外部狀態。
## 享元模式的通用結構
- 創建工廠,只有當某種共享對象被真正需要時,它才從工廠中被創建出來。
- 管理器來記錄對象相關的外部狀態,使這些外部狀態通過某個鉤子和共享對象聯系起來。
## 文件上傳的例子
改造前:
```javascript
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 = '...';
document.body.appendChild(this.dom);
};
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 ++);
}
};
startUpload('plugin', [
{
fileName: '1.txt',
fileSize: 1000
},
...
]);
```
上面的代碼中有多少個需要上傳的文件,就一共創建了多少個`upload`對象。
重構:
```javascript
// uploadType作為內部狀態,再抽離外部狀態
var Upload = function(uploadType){
this.uploadType = uploadType;
};
// 定義刪除文件的方法
Upload.prototype.delFile = function(id){
uploadManager.setExternalState(id, this); // 設置外部狀態
if(this.fileSize < 3000){
return this.dom.parentNode.removeChild(this.dom);
}
if(window.confirm('確定要刪除文件嗎?'+ file.fileName)){
return this.dom.parentNode.removeChild(this.dom);
}
};
// 工廠進行對象實例化
var UploadFactory = (function(){
var createFlyWeightObjs = {};
return {
create: function(uploadType){
if(createFlyWeightObjs[uploadType]){
return createFlyWeightObjs[uploadType];
}
return createFlyWeightObjs[uploadType] = new Upload(uploadType);
}
}
})();
// 管理器封裝外部狀態
var uploadManager = (function(){
var uploadDataBase = {}; // 存儲外部狀態
return {
add: function(id, uploadType, fileName, fileSize){
var flyWeightObj = UploadFactory.create(uploadType);
var dom = document.createElement('div');
dom.innerHTML = '...';
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];
}
}
}
})();
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
},
...
]);
```
## 享元模式的適用性
- 一個程序中使用了大量的相似對象。
- 由于使用了大量對象,造成很大的內存開銷
- 對象的大多數狀態都可以變為外部狀態
- 剝離出對象的外部狀態之后,可以用相對較少的共享對象取代大量對象。
## 再談談內部狀態和外部狀態
實現享元模式的關鍵是把內部狀態和外部狀態分離開來。有多少種內部狀態的組合,系統中便最多存在多少個共享對象,而外部狀態儲存在共享對象的外部,在必要時被傳入共享對象來組裝成一個完整的對象。
## 對象池
對象池維護一個裝載空閑對象的池子,如果需要對象的時候,不是直接`new`,而是轉從對象池里獲取。如果對象池沒有空閑對象,則創建一個新的對象,當獲取出的對象完成它的職責之后,再進入池子等待被下次獲取。
### 通用對象池
```javascript
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;
iframeFactory.recover(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);
```
對象池是另外一種性能優化方案,它跟享元模式有一些相似之處,但沒有分離內部狀態和外部狀態這個過程。