[JavaScript] 函數節流(throttle)和函數防抖(debounce)


js 的函數節流(throttle)和函數防抖(debounce)概述

函數防抖(debounce)

一個事件頻繁觸發,但是我們不想讓他觸發的這么頻繁,於是我們就設置一個定時器讓這個事件在 xxx 秒之后再執行。如果 xxx 秒內觸發了,則清理定時器,重置等待事件 xxx 秒
比如在拖動 window 窗口進行 background 變色的操作的時候,如果不加限制的話,隨便拖個來回會引起無限制的頁面回流與重繪
或者在用戶進行 input 輸入的時候,對內容的驗證放在用戶停止輸入的 300ms 后執行(當然這樣不一定好,比如銀行卡長度驗證不能再輸入過程中及時反饋)

一段代碼實現窗口拖動變色

  <script>
    let body = document.getElementsByTagName("body")[0];
    let index = 0;
    if (body) {
      window.onresize = function() {
        index++;
        console.log("變色" + index + "次");
        let num = [Math.ceil(Math.random() * 255), Math.ceil(Math.random() * 255), Math.ceil(Math.random() * 255)];
        body.style.background = "rgb(" + num[0] + "," + num[1] + "," + num[2] + ")"; //晃瞎眼睛
      };
    }
  </script>

簡單的防抖

使用 setTimeout 進行延遲處理,每次觸發事件時都清除掉之前的方法

  <script>
    let body = document.getElementsByTagName("body")[0];
    let index = 0;
    let timer = null;
    if (body) {
      window.onresize = function() {
        //如果在一秒的延遲過程中再次觸發,就將定時器清除,清除完再重新設置一個新的
        clearTimeout(timer);

        timer = setTimeout(function() {
          index++;
          console.log("變色" + index + "次");
          let num = [Math.ceil(Math.random() * 255), Math.ceil(Math.random() * 255), Math.ceil(Math.random() * 255)];
          body.style.background = "rgb(" + num[0] + "," + num[1] + "," + num[2] + ")";
        }, 1000); //反正就是等你什么都不干一秒后才會執行代碼
      };
    }
  </script>

抽離 debounce

但是目前有一個問題,就是代碼耦合,這樣不夠優雅,將防抖和變色分離一下

  <script>
    let body = document.getElementsByTagName("body")[0];
    let index = 0;
    let lazyLayout = debounce(changeBgColor, 1000);
    window.onresize = lazyLayout;

    function changeBgColor() {
      index++;
      console.log("變色" + index + "次");
      let num = [Math.ceil(Math.random() * 255), Math.ceil(Math.random() * 255), Math.ceil(Math.random() * 255)];
      body.style.background = "rgb(" + num[0] + "," + num[1] + "," + num[2] + ")";
    }

    //函數去抖(連續事件觸發結束后只觸發一次)
    function debounce(func, wait) {
      let timeout, context, args; //默認都是undefined

      return function() {
        context = this;
        args = arguments;

        if (timeout) clearTimeout(timeout);

        timeout = setTimeout(function() {
          //執行的時候到了
          func.apply(context, args);
        }, wait);
      };
    }
  </script>

underscore.js 的 debounce

underscore.js 實現的 debounce 已經經過檢驗

//1.9.1
 _.debounce = function(func, wait, immediate) {
    var timeout, result;

    var later = function(context, args) {
      timeout = null;
      if (args) result = func.apply(context, args);
    };

    var debounced = restArguments(function(args) {
      if (timeout) clearTimeout(timeout);
      if (immediate) {
        var callNow = !timeout;
        timeout = setTimeout(later, wait);
        if (callNow) result = func.apply(this, args);
      } else {
        timeout = _.delay(later, wait, this, args);
      }

      return result;
    });

    debounced.cancel = function() {
      clearTimeout(timeout);
      timeout = null;
    };

    return debounced;
  };

函數節流(throttle)

簡單的節流

一個事件頻繁觸發,但是在 xxx 秒內只能執行一次代碼

//上面的變色在節流中就是這樣寫了
  <script>
    let doSomething = true;
    let body = document.getElementsByTagName("body")[0];
    let index = 0;

    window.onresize = function() {
      if (!doSomething) return;
      doSomething = false;
      setTimeout(function() {
        index++;
        console.log("變色" + index + "次");
        let num = [Math.ceil(Math.random() * 255), Math.ceil(Math.random() * 255), Math.ceil(Math.random() * 255)];
        body.style.background = "rgb(" + num[0] + "," + num[1] + "," + num[2] + ")";
        doSomething = true;
      }, 1000);
    };
  </script>

分離出 throttle 函數

跟上面的防抖差不多,分離一下,降低代碼的耦合度

<script>
    let body = document.getElementsByTagName("body")[0];
    let index = 0;
    let lazyLayout = throttle(changeBgColor, 1000);
    window.onresize = lazyLayout;

    function changeBgColor() {
      index++;
      console.log("變色" + index + "次");
      let num = [Math.ceil(Math.random() * 255), Math.ceil(Math.random() * 255), Math.ceil(Math.random() * 255)];
      body.style.background = "rgb(" + num[0] + "," + num[1] + "," + num[2] + ")";
    }

    //函數去抖(連續事件觸發結束后只觸發一次)
    function throttle(func, wait) {
      let context,
        args,
        doSomething = true;

      return function() {
        context = this;
        args = arguments;

        if (!doSomething) return;

        doSomething = false;

        setTimeout(function() {
          //執行的時候到了
          func.apply(context, args);
          doSomething = true;
        }, wait);
      };
    }
  </script>

underscore.js 中 throttle 函數實現

  _.throttle = function(func, wait, options) {
    var timeout, context, args, result;
    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;
    };

    var throttled = function() {
      var now = _.now();
      if (!previous && options.leading === false) previous = now;
      var remaining = wait - (now - previous);
      context = this;
      args = arguments;
      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) {
        timeout = setTimeout(later, remaining);
      }
      return result;
    };

    throttled.cancel = function() {
      clearTimeout(timeout);
      previous = 0;
      timeout = context = args = null;
    };

    return throttled;
  };

防抖和節流是用來干什么的?

防抖的用處

  • 綁定 scroll 滾動事件,resize 監聽事件
  • 鼠標點擊,執行一個異步事件,相當於讓用戶連續點擊事件只生效一次(很有用吧)
  • 還有就是輸入框校驗事件(但是不一定好使,比如校驗銀行卡長度,當你輸入完之后已經超出 100 個字符,正常應該是超出就提示錯誤信息)

節流的用處

  • 當然還是鼠標點擊啦,但是這個是限制用戶點擊頻率。類似於你拿把 ak47 射擊,槍的射速是 100 發/分鍾,但是的手速達到 1000 按/分鍾,就要限制一下嘍(防止惡意刷子)
  • 根據屏幕滾動到底部加載更多的功能

其實二者主要就是為了解決短時間內連續多次重復觸發和大量的 DOM 操作的問題,來進行性能優化(重點是同時還能接着辦事,並不耽誤)
防抖主要是一定在 xxx 秒后執行,而節流主要是在 xxx 內執行(時間之后,時間之內)

右邊那個快速目錄就是加了個 throttle,控制台的執行速度就減少了(快速目錄是看了掘金的目錄之后弄的,確實方便了好多,對於長文本的閱讀體驗好了不少)

文章寫的時候用的 underscore 1.8.2 版本,實現也是參考 underscore 的源碼,實現方式與 underscore 最新有些代碼還是不太一樣了。(功能還是相同的)


免責聲明!

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



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