函式節流

chenhao_ch發表於2015-04-23

函式節流介紹

頁面在繫結resize,keydown或者mousemove這些能連續觸發的事件時,使用者只要很常規的操作,就能連續觸發多次繫結的方法。當繫結方法裡面存在大量的類似於DOM操作這種極其消耗效能的程式碼時,會直接導致頁面執行的卡頓。這個時候就會用到函式節流。

函式節流的實現

函式節流最普通的實現就是透過取摩操作來過濾部分執行。參考程式碼如下

javascriptvar mousemoveCount = 0;
function mousemoveListener(e){
  mousemoveCount++;
  if(mousemoveCount % 2 === 0){
    return;
  }
  console.info('業務邏輯');
}

當第一次觸發並執行mousemoveListener事件時,會列印“業務邏輯”;緊接著第二次執行mousemoveListener事件時,由於mousemoveCount為2,會直接return掉,並不會列印“業務邏輯”。這樣子,就實現了函式節流,存在複雜計算的業務邏輯執行次數減半了。

但是這種實現存在兩個問題:

  1. 方法的執行頻率(或者說幀率)是不可控的。比如mousemove事件,執行頻率由滑鼠移動速度決定,由上面這種方式實現,頻率還是由滑鼠移動速度決定。
  2. 最後一次觸發可能未執行。比如當最後一次觸發事件時,mousemoveCount是偶數,那麼會直接return。如果業務需要最後一次必須執行業務邏輯,則會存在bug。

所以就有了下面的最佳化實現(throttle和debounce)。

throttle實現

throttle又叫函式節流,思路是控制某一個時間段(執行週期)內觸發的事件,只會執行一次業務邏輯。程式碼如下:

javascriptvar lastMousemoveTime = 0, mousemoveTime = 100;
function mousemoveListener(e){
  var now = new Date().getTime();
  if(now - lastMousemoveTime <= mousemoveTime) {
    return;
  }
  lastMousemoveTime = now;
  setTimeout(function(){
    console.info('業務邏輯');
  }, mousemoveTime);
}

第一次觸發mousemove會設定100ms後執行業務邏輯,在這之後的100ms裡面觸發的mousemove都不會觸發業務邏輯。相當於控制了mousemove事件100ms觸發一次,也就是10幀。

使用這種實現(throttle),可以做到觸發頻率可控。但當業務希望連續的觸發事件只在之後一次觸發後才執行業務邏輯,比如resize事件,只希望視窗變化結束後才進行業務邏輯的執行,throttle實現就不適用了。這個時候就需要使用到debounce

debounce實現

debounce又叫函式去抖動,思路是業務邏輯在resize不在觸發後才執行。程式碼如下:

javascriptvar resizeTimer = null;
function resizeListener(e){
  if(resizeTimer) {
    clearTimerout(resizeTimer);
  }
  resizeTimer = setTimeout(function(){
    console.info('業務邏輯');
  }, 100);
}

但resize連續快速觸發時,業務邏輯並不會執行。只有當最後一次觸發resize後100ms,才執行業務邏輯。這種情況就能實現只在最後一次resize觸發業務計算了。

underscore 中已經對throttle和debounce做了實現和封裝, 有興趣可以去檢視原始碼。

相關文章