面試之手寫防抖節流

面試之手寫防抖節流

關注前端體驗或性能優化的應該有聽說過防抖節流。那麼,什麼是防抖節流呢?

防抖

概念

在短時間內多次觸發同一個函數,只執行最後一次。

舉例:搭乘公交車的時候,陸續有不同的乘客上車,但師傅只會在最後一個乘客上車後才關門。

效果演示

防抖前

01

防抖後

02

應用場景

  • 表單輸入驗證

  • 表單輸入觸發搜索 ajax

  • resize/scroll/touch/mouseove 事件

實現

簡單版本

function debounce(fn, wait = 1000) {
  let timer = null;

  return function debounced(...args) {
    // 重置計時器
    if (timer) clearTimeout(timer);

    // 新計時器
    timer = setTimeout(() => {
      fn(...args);
      timer = null;
    }, wait);
  };
}

可以看出debounce函數的實現原理就是通過計時器延遲函數執行,短時間內再次觸發時重置並添加新計時器。此時的輸出函數還有個缺陷,就是this指向global,我們需要讓它指向原本指向的變量。

function debounce(fn, wait = 1000) {
  let timer = null;

  return function debounced(...args) {
    // 重置計時器
    if (timer) clearTimeout(timer);

    // 新計時器
    timer = setTimeout(() => {
      fn.apply(this, ...args);
      timer = null;
    }, wait);
  };
}

現在我們實現了一個簡單的防抖函數。有時候我們會要求函數在第一次觸發立即執行,我們來為它添加個參數。

function debounce(fn, wait = 1000, immediate = false) {
  let timer = null;

  return function debounced(...args) {
    // 重置計時器
    if (timer) clearTimeout(timer);

    // 首次立即執行
    if (immediate && !timer) {
      fn.apply(this, ...args);

      timer = setTimeout(() => {
        timer = null;
      }, wait);

      return;
    }

    // 新計時器
    timer = setTimeout(() => {
      fn.apply(this, ...args);
      timer = null;
    }, wait);
  };
}

我們還可以為其添加取消的功能。

function debounce(fn, wait = 1000, immediate = false) {
  let timer = null;

  function debounced(...args) {
    // 重置計時器
    if (timer) clearTimeout(timer);

    // 首次立即執行
    if (immediate && !timer) {
      fn.apply(this, ...args);

      timer = setTimeout(() => {
        timer = null;
      }, wait);

      return;
    }

    // 新計時器
    timer = setTimeout(() => {
      fn.apply(this, ...args);
      timer = null;
    }, wait);
  }

  debounced.cancel = () => {
    clearTimeout(timer);
    timer = null;
  };

  return debounced;
}

此時一個功能完備的debounce函數就完成了。

節流

概念

多次觸發同一個函數,同一段時間內只執行一次。

舉例:獲取驗證碼很多都會限制 60s 的時間,在 60s 內再次獲取驗證碼是無效,只能獲取一次。下個60s才能再次獲取。

效果演示

節流前

01

節流後

03

應用場景

  • 編輯器語法校驗

  • resize/scroll/touch/mouseove 事件

  • 表單輸入聯想

實現

簡單版本

function throttle(fn, wait = 1000) {
  let previous = 0;

  const throttled = (...args) => {
    const now = +new Date();

    if (now - previous > wait) {
      fn.apply(this, args);
      previous = now;
    }
  };

  return throttled;
}

可以看出節流的主要原理就是利用時間差(當前和上次執行)來過濾中間過程觸發的函數執行。我們現在為其添加參數來控制是否在開始時會立即觸發一次,及最後一次觸發是否執行。

function throttle(fn, wait, options = { leading: true, trailing: false }) {
  let timer;
  let previous = 0;

  const { leading, trailing } = options;

  const throttled = function (...args) {
    const now = +new Date();

    if (leading === false && !previous) previous = now;
    if (timer) clearTimeout(timer);

    if (now - previous > wait) {
      fn.apply(this, args);
      previous = now;
    } else if (trailing) {
      // 更新timer
      timer = setTimeout(() => {
        fn.apply(this, args);
        previous = 0;
        timer = null;
      }, wait);
    }
  };

  return throttled;
}

我們還可以為其添加取消的功能。

function throttle(fn, wait, options = { leading: true, trailing: false }) {
    let timer;
    let previous = 0;

    const { leading, trailing } = options;

    const throttled = function (...args) {
        const now = +new Date();

        if (leading === false && !previous) previous = now;
        if (timer) clearTimeout(timer);

        if (now - previous > wait) {
            fn.apply(this, args);
            previous = now;
        } else if (trailing) {
            // 更新timer
            timer = setTimeout(() => {
                fn.apply(this, args);
                previous = 0;
                timer = null;
            }, wait);
        }
    }
    throttled.cancel = () => {
        clearTimeout(timer);
        timer = null;
        previous = 0;
    }


    return throttled;
}

此時一個功能完備的throttle函數也完成了。

總結

防抖和節流是兩個在工作中很可能會遇到的問題,弄清楚其作用和原理對技能提升和面試都會有幫助。

參考


歡迎到前端學習打卡群一起學習~516913974

Tags: