函數節流和防抖

函數的高階使用2

咱們來看一個分析:
若是要實現一個拖拽功能,須要一路監聽 mousemove 事件,在回調中獲取元素當前位置,而後重置dom的位置來進行樣式改變。若是不加以控制,每移動必定像素而觸發的回調數量很是驚人,回調中又伴隨着 DOM 操做,繼而引起瀏覽器的重排與重繪,性能差的瀏覽器可能就會直接假死.html

在某些狀況下會引起函數被很是頻繁地調用,而形成大的性能問題。解決性能問題的處理辦法就是函數節流和函數防抖。前端

函數防抖

是函數在特定的時間內不被再調用後執行。 也就是讓某個函數在上一次執行後,知足等待某個時間內再也不觸發此函數後再執行,而在這個等待時間內再次觸發此函數,等待時間會從新計算。ajax

應用場景

  1. 用戶註冊時候手機號碼驗證和郵箱驗證只有等到用戶輸入完畢後,前端才須要檢查格式是否正確,若是不正確,再彈出提示語。
  2. scroll/resize事件
  3. 文本連續輸入,ajax驗證/關鍵字搜索'

實現方式

  • 函數防抖的要點,也是須要一個setTimeout來輔助實現。延遲執行須要跑的代碼。
  • 若是方法屢次觸發,則把上次記錄的延遲執行代碼用clearTimeout清掉,從新開始。
  • 若是計時完畢,沒有方法進來訪問觸發,則執行代碼。
    舉個例子: 在移動鼠標時觸發打印函數 加上函數防抖。
var timer = false;
document.querySelector("#div").mousemove = function(){
    clearTimeout(timer);  // 當事件觸發的時候,清除以前等待執行的函數,
    timer = setTimeout(function(){ //  開啓新的延時執行函數
        console.log("函數防抖");
    }, 300);
};

咱們如今對上述方法封裝一下瀏覽器

// 函數防抖
const debounce = function(fn, wait=300){
    return function(){
        clearTimeout(fn.timer);  // 當事件觸發的時候,清除以前等待執行的函數,
        fn.timer = setTimeout(fn, 300);
    }
}

可是咱們就會發現 fn 的this 指向發生了改變, fn參數是接收不到的。app

function debounce = function(fn, wait=300){
    return function(){
        clearTimeout(fn.timer);  // 當事件觸發的時候,清除以前等待執行的函數,
        fn.timer = setTimeout(fn.bind(this), wait);
    }
}

或者使用applydom

function debounce = function(fn, wait=100){
    return function(){
        clearTimeout(fn.timer);  // 當事件觸發的時候,清除以前等待執行的函數,
        fn.timer = setTimeout( ()=>{ // 不能寫匿名函數,this會發生改變
            return fn.apply(this, arguments); 
        }, wait);
    }
}

函數節流

函數節流,即限制函數的執行頻率,在持續觸發事件的狀況下,間斷地執行函數。只要當前函數沒有執行完成,任何新觸發的函數都會被忽略。就是在固定的時間間隔內函數只會被調用一次。異步

適用場景

  1. 頻繁的mousemove/keydown,好比高頻的鼠標移動,遊戲射擊類的
  2. 搜索聯想: 監聽keypress事件,而後異步去查詢結果. 若是快速輸入過多字符串, 就會觸發高頻請求。
  3. 監聽滾動事件判斷是否到頁面底部自動加載更多(scroll事件)

實現簡易

  • 函數節流的要點是,聲明一個變量當標誌位,記錄當前代碼是否在執行。
  • 若是空閒,則能夠正常觸發方法執行。
  • 若是代碼正在執行,則取消此次方法執行,直接return。
var timer = false;
document.querySelector("#div").mousemove = function(){
    if(timer)
        return;
    timer = setTimeout(function(){   //  正在執行
        console.log("函數節流");
        timer = false; // 表示不在執行
    }, 100);
};

在來改寫一下:函數

const throttle = function(fn, wait=100){
    return function(){
        if(fn.timer){ return; }
        fn.timer = setTimeout(()=>{
            fn.apply(this,arguments);
            fn.timer = false;
        },wait)
    }
}

總結

函數的節流和函數的去抖都是經過減小實際邏輯處理過程的執行來提升事件處理函數運行性能的手段,並無實質上減小事件的觸發次數。
某些函數確實是用戶主動調用的,但由於一些客觀的緣由,這些函數會嚴重地影響頁面性能。解決方案:例如分頁技術和延遲加載,均可以免在頁面上同時加載過多數據,形成的頁面卡頓。甚至假死現象。其實,優化就是合理利用性能。性能

underscore v1.7.0相關的源碼實現

_.debounce = function(func, wait, immediate) {
    // immediate默認爲false
    var timeout, args, context, timestamp, result;

    var later = function() {
      // 當wait指定的時間間隔期間屢次調用_.debounce返回的函數,則會不斷更新timestamp的值,致使last < wait && last >= 0一直爲true,從而不斷啓動新的計時器延時執行func
      var last = _.now() - timestamp;

      if (last < wait && last >= 0) {
        timeout = setTimeout(later, wait - last);
      } else {
        timeout = null;
        if (!immediate) {
          result = func.apply(context, args);
          if (!timeout) context = args = null;
        }
      }
    };

    return function() {
      context = this;
      args = arguments;
      timestamp = _.now();
      // 第一次調用該方法時,且immediate爲true,則調用func函數
      var callNow = immediate && !timeout;
      // 在wait指定的時間間隔內首次調用該方法,則啓動計時器定時調用func函數
      if (!timeout) timeout = setTimeout(later, wait);
      if (callNow) {
        result = func.apply(context, args);
        context = args = null;
      }

      return result;
    };
  };
_.throttle = function(func, wait, options) {
    /* options的默認值
     *  表示首次調用返回值方法時,會立刻調用func;不然僅會記錄當前時刻,當第二次調用的時間間隔超過wait時,才調用func。
     *  options.leading = true;
     * 表示當調用方法時,未到達wait指定的時間間隔,則啓動計時器延遲調用func函數,若後續在既未達到wait指定的時間間隔和func函數又未被調用的狀況下調用返回值方法,則被調用請求將被丟棄。
     *  options.trailing = true; 
     * 注意:當options.trailing = false時,效果與上面的簡單實現效果相同
     */
    var context, args, result;
    var timeout = null;
    var previous = 0;
    if (!options) options = {};
    var later = function() {
      previous = options.leading === false ? 0 : _.now();
      timeout = null;
      result = func.apply(context, args);
      if (!timeout) context = args = null;
    };
    return function() {
      var now = _.now();
      if (!previous && options.leading === false) previous = now;
      // 計算剩餘時間
      var remaining = wait - (now - previous);
      context = this;
      args = arguments;
      // 當到達wait指定的時間間隔,則調用func函數
      // 精彩之處:按理來講remaining <= 0已經足夠證實已經到達wait的時間間隔,但這裏還考慮到假如客戶端修改了系統時間則立刻執行func函數。
      if (remaining <= 0 || remaining > wait) {
        // 因爲setTimeout存在最小時間精度問題,所以會存在到達wait的時間間隔,但以前設置的setTimeout操做還沒被執行,所以爲保險起見,這裏先清理setTimeout操做
        if (timeout) {
          clearTimeout(timeout);
          timeout = null;
        }
        previous = now;
        result = func.apply(context, args);
        if (!timeout) context = args = null;
      } else if (!timeout && options.trailing !== false) {
        // options.trailing=true時,延時執行func函數
        timeout = setTimeout(later, remaining);
      }
      return result;
    };
  };

參考[http://www.cnblogs.com/fsjohnhuang/p/4147810.html]優化

相關文章
相關標籤/搜索