代理模式

寫在前面

參考《JavaScript設計模式與實戰》


代理模式

代理模式顧名思義就是爲一個對象進行代理,對該對象的操作都會通過代理先去完成,達到爲對象進行過濾保護等功能。

保護代理

通過代理,可以過濾掉一些不符合條件的操作,從而使得通過代理的那些操作可以操作對象。

虛擬代理

虛擬代理可以在真正需要某個東西的時候纔去創建,這樣可以減少不必要的開銷。


應用:圖片預加載

圖片預加載

圖片在開始被加載到加載結束的過程中可能會有一段較長的空白期,這樣的體驗並不好。如果在此過程中添加一個loading的動圖,讓用戶知道圖片正在加載,就可以提升用戶體驗。

通過代理實現

不實現預加載的效果是這樣的

var myImg = (function(){
  var imgNode = document.createElement('img');
  document.body.append(imgNode);
  return {
    setSrc: function(src){
      imgNode.src = src;
    }
  };
})();
myImg.setSrc('https://www.google.co.jp/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png');

現在,加上代理。代理實現的就是先設置圖片的src爲loading,一旦加載完成後,就改爲正常的圖片。

var myImg = (function(){
  var imgNode = document.createElement('img');
  document.body.append(imgNode);
  return {
    setSrc: function(src){
      imgNode.src = src;
    }
  };
})();
var proxyImg = (function(){
  var proxyImg = document.createElement('img');
  proxyImg.onload = function() {
    myImg.setSrc(proxyImg.src);
  }
  return {
    setSrc: function(src) {
      proxyImg.src = src;
      myImg.setSrc('loading.png')
    }
  }
})();
proxyImg.setSrc('normal.png');

首先,代理會設置圖片的src屬性爲loading圖片,並創建一個新的img節點用於監聽圖片的onload事件。
書中有提到,不是用代理其實也可以實現(只需要在myImg多添加一個img節點監聽onload時間即可)。
那爲什麼需要代理呢?

  • 代理實現了JS中的單一職責原則,也就是說myImg處既然實現的職責是顯示圖片,那麼再添加一個預加載圖片的職責就成了多職責了。多職責的壞處體現在強耦合。
    按道理來講,圖片的預加載只不過是錦上添花的一個效果。如果某一天我們不需要了,刪除這個效果就需要操作myImg對象。

也可以看到proxyImgmyImg擁有相同的方法,這也是有它的道理的。

  • 代理和被代理的對象擁有相同的方法這一操作很好地實現了封裝,也就是說,用戶不會在意你內部是如何進行的代理,對他來說設置圖片只需要調用setSrc即可。
    如果有一天不需要預加載了,也只需要把方法的對象改爲myImg,不需要再去學習myImg中設置圖片的方法。

應用:合併HTTP請求

合併HTTP請求可以大大提高前端性能,合併HTTP請求可不僅僅在瀏覽器請求頁面的時候。
在前些天我寫了一個vue+express的博客應用時,遇到過用戶增刪改鏈接的時候,我當時想的是如果每次操作都要去訪問一次數據庫,再加上頻繁操作,不斷去發送http請求將是一件多麼大的開銷。
於是我添加了一個按鈕讓用戶自己去點擊保存。實際上這並不合適。
看了這本書後,發現可以通過代理的方式進行保存操作。

  • 將修改後的數據保存在數組中,每次用戶操作後的結果都保存在數組中。這樣,只需要在一段時間後,讓代理髮送http請求併發送數組中的數據進行保存即可。

既然這裏提到了,我就使用這個設計模式進行修改一下。
大概的感覺如下
這裏寫圖片描述
在我的項目中,代理的感覺可能比較不明顯。主要是在state中維護的links作爲存放數據的地方。
一旦點擊添加按鈕併成功添加,就會觸發一個定時器,這個定時器會把5秒內的所有操作的最後結果提交上去,並清除定時器以便下次使用。

if(!this.timer) {
  this.timer = setTimeout(()=>{
    this.saveLinks(this.links);
    clearTimeout(this.timer);
    this.timer = null;
  }, 5000);
}

linkstimer共同爲代理,links存放處理後的數據,timer則定時將它們上傳到服務器。


應用:惰性加載

書中還提到了代理在惰性加載中的應用。
惰性加載就是需要時再加載。作者舉了一個控制檯的例子,這個例子描述的是用戶可以使用console.log(xxx)來在控制檯查看輸出,但是前提是用戶必須先打開了控制檯。
如果用戶在沒有打開控制檯的情況下執行了console.log語句,那麼就需要先保存語句,到控制到打開後執行。
代理是這樣寫的:

var cache = [];  
var miniConsole = {     
    log: function(){         
        var args = arguments;         
        cache.push( function(){
            return miniConsole.log.apply( miniConsole, args );         
        });
    } 
};  
miniConsole.log(1); 

監聽用戶按下F2按鈕:

var handler = function( ev ){     
    // 加載真正的腳本文件,並執行
    if ( ev.keyCode === 113 ){         
        var script = document.createElement( 'script' );         
        script.onload = function(){
            for ( var i = 0, fn; fn = cache[ i++ ]; ){   
                 fn();             
            }
        };        
        script.src = 'miniConsole.js'; 
        document.getElementsByTagName( 'head' )[0].appendChild( script );
    } 
};
// 監聽用戶按下F2
document.body.addEventListener( 'keydown', handler, false ); 

真正的腳本文件:

miniConsole = {
    log: function(){
        console.log(Array.prototype.join.call(arguments));
    }
}

應用:緩存代理

緩存代理大部分的使用場景是,在用戶輸入相同的參數時得到的都是一樣的,那麼這個時候就不需要再多執行一次只需要從緩存中取即可。
作者舉了一個乘積的例子,如果多次參數相同就不需要重新計算乘積

var multi = function() {
  var result = 1;
  for(var i = 0; i < arguments.length; i++) {
    result = result * arguments[i];
  }
  return result;
}
var proxyMulti = (function(){
  var cache = {};
  return function() {
    var args = Array.from(arguments).join(',');
    if(cache[args]) {
      alert('c')
      return cache[args];
    } else {
      cache[args] = multi.apply(this, arguments);
      return cache[args];
    }
  }
})();

console.log(proxyMulti(2,3)); // 6 計算
console.log(proxyMulti(2,3)); // 6 緩存

通用版本

代理只關心緩存中是否存在,而不關心是哪一個操作的代理。

var proxyMulti = function(fn){
  var cache = {};
  return function() {
    var args = Array.from(arguments).join(',');
    if(cache[args]) {
      alert('c');
      return cache[args];
    } else {
      cache[args] = fn.apply(this, arguments);
      return cache[args];
    }
  }
}
let multiProxy = proxyMulti(multi);
console.log(multiProxy(2,3)); // 6 計算
console.log(multiProxy(2,3)); // 6 緩存
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章