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

享元(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();
};
//只須要兩個對象便完成了一樣的功能
複製代碼

如何使用享元模式

享元模式要求將對象的屬性劃分爲內部狀態與外部 狀態(狀態在這裏一般指屬性)。享元模式的目標是儘可能減小共享對象的數量,關於如何劃份內部狀態和外部狀態,下面的幾條經驗提供了一些指引bash

  • 內部狀態存儲於對象內部
  • 內部狀態能夠被一些對象共享
  • 內部狀態獨立於具體的場景,一般不會改變
  • 外部狀態取決於具體的場景,並根據場景而變化,外部狀態不能被共享

在上面的例子中,性別是內部狀態,內衣是外部狀態,經過區分這兩種狀態,大大減小了系 統中的對象數量。一般來說,內部狀態有多少種組合,系統中便最多存在多少個對象,由於性別 一般只有男女兩種,因此該內衣廠商最多隻須要 2 個對象。app

享元模式的替代方案 —— 對象池

對象池是另一種性能優化方案,它跟享元模式有一些類似之處,但沒有分離內部狀態和外 部狀態這個過程。對象池維護一個裝載空閒對象的池子,若是須要對象的時候,不是直接 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設計模式與開發實踐》最全知識點彙總大全post

相關文章
相關標籤/搜索