防抖(debounce)和 節流(throttling)


防抖(debounce)和 節流(throttling)

1、防抖和節流出現的原因

防抖和節流是針對響應跟不上觸發頻率這類問題的兩種解決方案。

  • 在給DOM綁定事件時,有些事件我們是無法控制觸發頻率的。 如鼠標移動事件onmousemove, 滾動滾動條事件onscroll,窗口大小改變事件onresize,瞬間的操作都會導致這些事件會被高頻觸發。 如果事件的回調函數較為復雜,就會導致響應跟不上觸發,出現頁面卡頓,假死現象。

  • 在實時檢查輸入時,如果我們綁定onkeyup事件發請求去服務端檢查,用戶輸入過程中,事件的觸發頻率也會很高,會導致大量的請求發出,響應速度會大大跟不上觸發。

ps:防抖和節流的作用都是防止函數多次調用。區別在於,假設一個用戶一直觸發這個函數,且每次觸發函數的間隔小於wait,防抖的情況下只會調用一次,而節流的 情況會每隔一定時間(參數wait)調用函數。

防抖

實現簡單防抖


function debounce(fn, wait) {
    // 緩存定時器
    var timer = null;    
    return function(...args) {
        if (timer) clearTimeout(timer)
            timer = setTimeout(() => {
            fn.apply(this, args)
        }, wait)
	}
}

// 處理函數
function handle() {    
	console.log('測試防抖'); 
}

// 滾動事件
window.addEventListener('scroll', debounce(handle, 1000));

上面的防抖函數沒有立即執行事件,

實現帶有立即執行的防抖函數


/**
 * 防抖函數,返回函數連續調用時,空閑時間必須大於或等於 wait,func 才會執行
 *
 * @param  {function} func        回調函數
 * @param  {number}   wait        表示時間窗口的間隔
 * @param  {boolean}  immediate   設置為ture時,是否立即調用函數
 * @return {function}             返回客戶調用函數
 */
function debounce (func, wait = 50, immediate = true) {
  let timer, context, args;

  // 延遲執行函數
  const later = () => setTimeout(() => {
      timer = null;
    // 延遲執行的情況下,函數會在延遲函數中執行
    // 使用到之前緩存的參數和上下文
    if (!immediate) {
        func.apply(context, args)
        context = args = null
    }
   
  },wait)

   return function(...params) {
    // 如果沒有創建延遲執行函數(later),就創建一個
    if (!timer) {
      timer = later()
      // 如果是立即執行,調用函數
      // 否則緩存參數和調用上下文
      if (immediate) {
        func.apply(this, params)
      } else {
        context = this
        args = params
      }
    // 如果已有延遲執行函數(later),調用的時候清除原來的並重新設定一個
    // 這樣做延遲函數會重新計時
    } else {
      clearTimeout(timer)
      timer = later()
    }
  }
}

節流

防抖動和節流本質是不一樣的。防抖動是將多次執行變為最后一次執行,節流是將多次執行變成每隔一段時間執行。

實現節流


/**
 * underscore 節流函數,返回函數連續調用時,func 執行頻率限定為 次 / wait
 *
 * @param  {function}   func      回調函數
 * @param  {number}     wait      表示時間窗口的間隔
 * @param  {object}     options   如果想忽略開始函數的的調用,傳入{leading: false}。
 *                                如果想忽略結尾函數的調用,傳入{trailing: false}
 *                                兩者不能共存,否則函數不能執行
 * @return {function}             返回客戶調用函數
 */
_.throttle = function(func, wait, options) {
    var context, args, result;
    var timeout = null;
    // 之前的時間戳
    var previous = 0;
    // 如果 options 沒傳則設為空對象
    if (!options) options = {};
    // 定時器回調函數
    var later = function() {
      // 如果設置了 leading,就將 previous 設為 0
      // 用於下面函數的第一個 if 判斷
      previous = options.leading === false ? 0 : _.now();
      // 置空一是為了防止內存泄漏,二是為了下面的定時器判斷
      timeout = null;
      result = func.apply(context, args);
      if (!timeout) context = args = null;
    };
    return function() {
      // 獲得當前時間戳
      var now = _.now();
      // 首次進入前者肯定為 true
	  // 如果需要第一次不執行函數
	  // 就將上次時間戳設為當前的
      // 這樣在接下來計算 remaining 的值時會大於0
      if (!previous && options.leading === false) previous = now;
      // 計算剩余時間
      var remaining = wait - (now - previous);
      context = this;
      args = arguments;
      // 如果當前調用已經大於上次調用時間 + wait
      // 或者用戶手動調了時間
 	  // 如果設置了 trailing,只會進入這個條件
	  // 如果沒有設置 leading,那么第一次會進入這個條件
	  // 還有一點,你可能會覺得開啟了定時器那么應該不會進入這個 if 條件了
	  // 其實還是會進入的,因為定時器的延時
	  // 並不是准確的時間,很可能你設置了2秒
	  // 但是他需要2.2秒才觸發,這時候就會進入這個條件
      if (remaining <= 0 || remaining > wait) {
        // 如果存在定時器就清理掉否則會調用二次回調
        if (timeout) {
          clearTimeout(timeout);
          timeout = null;
        }
        previous = now;
        result = func.apply(context, args);
        if (!timeout) context = args = null;
      } else if (!timeout && options.trailing !== false) {
        // 判斷是否設置了定時器和 trailing
	    // 沒有的話就開啟一個定時器
        // 並且不能不能同時設置 leading 和 trailing
        timeout = setTimeout(later, remaining);
      }
      return result;
    };
  };

本文主要參考:前端面試之道


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM