Day7 - 弄懂JS的防抖和節流

對這兩個的定義一直是理解以後又忘記,因此就記下來方便下次查看javascript

防抖(debounce)

概念:在指定的時間內只執行一次回調函數,若在指定時間內又觸發了該事件,則會基於此刻從新計算回調函數的執行時間。html

以咱們生活中乘車刷卡的情景舉例,只要乘客不斷地在刷卡,司機師傅就不能開車,乘客刷卡完畢以後,司機會等待幾分鐘,肯定乘客坐穩再開車。若是司機在最後等待的時間內又有新的乘客上車,那麼司機等乘客刷卡完畢以後,還要再等待一會,等待全部乘客坐穩再開車。java

// fn是咱們須要包裝的事件回調, delay是每次推遲執行的等待時間
function debounce(fn, delay) {
  let timer = null
  return function () {
    let context = this, args = arguments
    timer && clearTimeout(timer)
    timer = setTimeout(function () {
      fn.apply(context, args)
    }, delay)
  }
}

複製代碼

節流(throttle)

概念:預先設定一個執行週期,只有觸發事件間隔大於等於這個執行週期,纔會執行回調函數app

類比到生活中的水龍頭,擰緊水龍頭到某種程度會發現,每隔一段時間,就會有水滴流出。函數

// 一、 使用setTimeout實現
function throttle (fn, delay) {
  let flag = false
  if (!flag) {
    return function() {
      let context = this, args = arguments
      flag = setTimeout(() => {
        fn.apply(context, args)
        flag = true
      }, delay)
    }
  }
}


// 二、 經過比較兩次時間戳的間隔是否大於等於咱們事先指定的時間來決定是否執行事件回調
function throttle (fn, delay) {
  let start = 0
  return function () {
    if (now - start >= delay) {
        let context = this, args = arguments, now = new Date()
        fn.apply(context, args);
        start = now
    }
  }
}

複製代碼

對比兩種實現方式,能夠發現:ui

一、定時器的方式,只有在第一次觸發回調的時候纔會執行,若是最後一次觸發與前一次觸發時間間隔小於delay,則在delay以後fn也會執行。this

二、時間戳的方式,在頁面加載的時候就會開始計時,若是頁面加載時間大於delay,第一次觸發事件回調就會當即執行,不會延遲delay,若是最後一次觸發與前一次觸發時間間隔小於delayfn並不會執行。spa

因此咱們將二者結合起來就能夠實現,首次觸發事件能夠執行,最後的時間間隔小於delay也能夠執行。code

function throttle (fn, delay) {
  let start = 0, timer = null
  
  return function() {
    let context = this, args = arguments, now = new Date()
    
    // 計算剩餘時間
    let remaining = delay - (now - start)
    
    // 若是時間間隔超出了咱們設定的時間間隔閾值,那就不等了,不管如何要反饋給用戶一次響應
    if (remaining <= 0) {
      timer && clearTimeout(timer)
      timer = null;
      fn.apply(context, args)
      start = now
      
    } else {
      // 若是時間間隔小於咱們設定的時間間隔閾值,則爲本次觸發操做設立一個新的定時器
	timer = setTimeout(() => {
            fn.apply(context, args)
        }, delay)
    }
  }
}

複製代碼

應用場景

函數防抖是某一段時間內只執行一次,而函數節流是間隔時間執行htm

  • 函數防抖
    • 實時搜索,拖拽
  • 函數節流
    • 窗口調整(調整大小),頁面滾動(滾動),搶購時瘋狂點擊(鼠標按下)

underscore實現

一、_.throttle函數

_.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;
    };
  };
複製代碼

2.、_.debounce函數

_.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)與函數去抖(debounce)

十分鐘學會防抖和節流

相關文章
相關標籤/搜索