《JavaScript設計模式與開發實踐》模式篇(9)—— 享元模式

嗨呀豆豆呢發表於2018-12-18

享元(flyweight)模式是一種用於效能優化的模式,“fly”在這裡是蒼蠅的意思,意為蠅量級。享元模式的核心是運用共享技術來有效支援大量細粒度的物件。 如果系統中因為建立了大量類似的物件而導致記憶體佔用過高,享元模式就非常有用了。在 JavaScript 中,瀏覽器特別是移動端的瀏覽器分配的記憶體並不算多,如何節省記憶體就成了一件非常有意義的事情。

故事背景

假設有個內衣工廠,目前的產品有 50 種男式內衣和 50 種女士內衣,為了推銷產品,工廠決定生產一些塑料模特來穿上他們的內衣拍成廣告照片。 正常情況下需要 50個男模特和50個女模特,然後讓他們每人分別穿上一件內衣來拍照。

程式碼實現(未使用享元模式)

var Model = function( sex, underwear){
    this.sex = sex;
    this.underwear = underwear;
};
Model.prototype.takePhoto = function(){
    console.log( 'sex= ' + this.sex + ' underwear=' + this.underwear);
};
for ( var i = 1; i <= 50; i++ ){
    var maleModel = new Model( 'male', 'underwear' + i );     
    maleModel.takePhoto();
 };
for ( var j = 1; j <= 50; j++ ){
    var femaleModel= new Model( 'female', 'underwear' + j );
    femaleModel.takePhoto(); 
};
複製程式碼

思考:真的需要如此多數量的物件嗎?

如上所述,現在一共有 50 種男內 衣和 50 種女內衣,所以一共會產生 100 個物件。如果將來生產了 10000 種內衣,那這個程式可能會因為存在如此多的物件已經提前崩潰。 下面我們來考慮一下如何優化這個場景。雖然有 100 種內衣,但很顯然並不需要 50 個男 模特和 50 個女模特。其實男模特和女模特各自有一個就足夠了,他們可以分別穿上不同的內衣來拍照。

程式碼重構(享元模式)

/*只需要區別男女模特
那我們先把 underwear 引數從建構函式中 移除,建構函式只接收 sex 引數*/
var Model = function( sex ){ 
    this.sex = sex;
};
Model.prototype.takePhoto = function(){
    console.log( 'sex= ' + this.sex + ' underwear=' + this.underwear);
};
/*分別建立一個男模特物件和一個女模特物件*/
var maleModel = new Model( 'male' ), 
    femaleModel = new Model( 'female' );
/*給男模特依次穿上所有的男裝,並進行拍照*/
for ( var i = 1; i <= 50; i++ ){ 
    maleModel.underwear = 'underwear' + i; 
    maleModel.takePhoto();
};
/*給女模特依次穿上所有的女裝,並進行拍照*/
for ( var j = 1; j <= 50; j++ ){ 
    femaleModel.underwear = 'underwear' + j; 
    femaleModel.takePhoto();
};
//只需要兩個物件便完成了同樣的功能
複製程式碼

如何使用享元模式

享元模式要求將物件的屬性劃分為內部狀態與外部 狀態(狀態在這裡通常指屬性)。享元模式的目標是儘量減少共享物件的數量,關於如何劃分內部狀態和外部狀態,下面的幾條經驗提供了一些指引

  • 內部狀態儲存於物件內部
  • 內部狀態可以被一些物件共享
  • 內部狀態獨立於具體的場景,通常不會改變
  • 外部狀態取決於具體的場景,並根據場景而變化,外部狀態不能被共享

在上面的例子中,性別是內部狀態,內衣是外部狀態,通過區分這兩種狀態,大大減少了系 統中的物件數量。通常來講,內部狀態有多少種組合,系統中便最多存在多少個物件,因為性別 通常只有男女兩種,所以該內衣廠商最多隻需要 2 個物件。

享元模式的替代方案 —— 物件池

物件池是另外一種效能優化方案,它跟享元模式有一些相似之處,但沒有分離內部狀態和外 部狀態這個過程。物件池維護一個裝載空閒物件的池子,如果需要物件的時候,不是直接 new,而是轉從物件池裡獲取。如 果物件池裡沒有空閒物件,則建立一個新的物件,當獲取出的物件完成它的職責之後, 再進入 池子等待被下次獲取。

通用物件池程式碼實現

/*通用的物件池*/
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 );
        }
    } 
};
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 );
複製程式碼

系列文章:

《JavaScript設計模式與開發實踐》最全知識點彙總大全

相關文章