理解防抖和節流的區別


背景:

在前端開發中,我們會經常需要綁定一些持續觸發的事件,如resize, scroll, mousemove等,但是有時候不希望在事件持續觸發的過程中太頻繁地執行函數

 

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div
      id="content"
      style="
        height: 150px;
        line-height: 150px;
        text-align: center;
        color: #fff;
        background-color: #ccc;
        font-size: 80px;
      "
    ></div>
    <script>
      let num = 1;
      const content = document.getElementById("content");
      function count() {
        content.innerHTML = num++;
      }
      content.onmousemove = count;
    </script>
  </body>
</html>

 

在上述代碼中,div 元素綁定了 mousemove 事件,當鼠標在 div(灰色)區域中移動的時候會持續地去觸發該事件導致頻繁執行函數

 

 

 

解決方案:防抖和節流

 

1. 防抖(debounce):是指觸發事件后n秒才執行函數,如果在n秒內又觸發事件了,則會重新計算函數時間

(case1:做電梯,電梯會等沒人進來了,再等一會,才關閉;case2: input輸入框,輸入內容搜索,在停下輸入過一會后,才發起請求獲取匹配的數據;case3: 玩游戲讀條,只有讀條結束才可以輸出,如果讀條被打斷,要重新讀條) 

 

1.1 非立即執行版防抖:事件觸發后不立即執行,n秒后執行

 

    <script>
      let num = 1;
      const content = document.getElementById("content");
      function count() {
        content.innerHTML = num++;
      }

      //   非立即執行版:觸發事件后函數不會立即執行,而是在n秒后執行,
      //   如果n秒內又觸發了事件,則會重新計算函數執行時間
      function debounce(func, wait) {
        let timeout;
        return function () {
          const context = this;
          const args = [...arguments];
          if (timeout) {
            clearTimeout(timeout);
          }
          timeout = setTimeout(() => {
            func.apply(context, args);
          }, wait);
        };
      }
      content.onmousemove = debounce(count, 1000);
    </script>

 

1.2 立即執行版防抖:觸發事件后函數立即執行,n秒內不觸發事件才能繼續執行

 

      //   立即執行版: 觸發事件后函數會立即執行,然后n秒內不觸發事件才能繼續執行的效果
      function debounce(func, wait) {
        let timeout;
        return function () {
          const context = this;
          const args = [...arguments];
          if (timeout) {
            clearTimeout(timeout);
          }
          const callNow = !timeout;
          timeout = setTimeout(() => {
            timeout = null;
          }, wait);
          if (callNow) {
            func.apply(context, args);
          }
        };
      }

 

1.3 結合版本:實際開發過程中,需要根據不同場景來決定使用哪一個版本的防抖函數。上述版本可以合並:

    
      /**
       * @desc 函數防抖
       * @param func 函數
       * @param wait 延遲執行毫秒數
       * @param immediate true 表立即執行,false 表非立即執行
       */

function debounce(func, wait, immediate) { let timeout; return function () { const context = this; const args = [...arguments]; if (timeout) { clearTimeout(timeout); } if (immediate) { const callNow = !timeout; timeout = setTimeout(() => { timeout = null; }, wait); if (callNow) { func.apply(context, args); } } else { timeout = setTimeout(() => { func.apply(context, args); }, wait); } }; } content.onmousemove = debounce(count, 1000, false);

 

 

2. 節流(throttle):是指連續觸發事件,但是在n秒中只執行一次的函數。節流會稀釋函數的執行頻率

(case 1: 看電影,每秒有24幀,意思是每1秒的動畫,播放了24張連續的圖片;case2: 滾動條加載更多,監聽滾動條位置時,設置每1s監聽一次,而不是無限次監聽;case3: 節流相當於玩游戲某技能的cd,cd時間未到不會執行)

 

2.1 時間戳版節流:在持續觸發事件的過程中,函數會立即執行,並且每1s執行一次

      function throttle(func, wait) {
        var previous = 0;
        return function () {
          let now = Date.now();
          let context = this;
          let args = arguments;
          if (now - previous > wait) {
            func.apply(context, args);
            previous = now;
          }
        };
      }

 

2.2 定時器版節流:在持續觸發事件的過程中,函數不會立即執行,每1s執行一次,在停止觸發事件后,函數會再執行一次

      function throttle(func, wait) {
        let timeout;
        return function () {
          let context = this;
          let args = arguments;
          if (!timeout) {
            timeout = setTimeout(() => {
              timeout = null;
              func.apply(context, args);
            }, wait);
          }
        };
      }

 

2.3 時間戳版本節流和定時器節流的區別是,時間戳版本的函數觸發是在時間段內開始的時候,而定時器版的函數觸發是在時間段內結束的時候。

 

兩者合並,如下版本:

/**
 * @desc 函數節流
 * @param func 函數
 * @param wait 延遲執行毫秒數
 * @param type 1 表時間戳版,2 表定時器版
 */
function throttle(func, wait ,type) {
    if(type===1){
        let previous = 0;
    }else if(type===2){
        let timeout;
    }
    return function() {
        let context = this;
        let args = arguments;
        if(type===1){
            let now = Date.now();

            if (now - previous > wait) {
                func.apply(context, args);
                previous = now;
            }
        }else if(type===2){
            if (!timeout) {
                timeout = setTimeout(() => {
                    timeout = null;
                    func.apply(context, args)
                }, wait)
            }
        }

    }
}

 

3. 防抖和節流的區別

  • 用戶在搜索的時候,在不停敲字,如果每敲一個字我們就要調一次接口,接口調用太頻繁,給卡住了。-----用防抖
  • 用戶在閱讀文章的時候,我們需要監聽用戶滾動到了哪個標題,但是每滾動一下就監聽,那樣會太過頻繁從而占內存,如果再加上其他的業務代碼,就卡住了。-----用節流

 

防抖:結合輸入框場景,只有當用戶輸入完畢一段時間后,才會調用接口,出現聯想詞

節流:指定時間間隔,只執行一次任務;應用場景:比如,懶加載監聽滾動條的位置,使用節流按一定頻率獲取

 

使用防抖和節流可以減少不必要的損耗(頻繁調取接口,網絡堵塞,增加服務器壓力)。

 

最后,匯總

函數防抖的時候,每次調用事件都是在正常執行暫停后一段時間
函數節流的時候,則是每隔一定的時間間隔就觸發一次
 


 

參考:

https://juejin.cn/post/6844903651278848014

https://juejin.cn/post/6844904185117278215

 


免責聲明!

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



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