防抖和節(jié)流

防抖和節(jié)流

防抖

在準(zhǔn)備執(zhí)行某項(xiàng)操作時(shí),預(yù)設(shè)一個(gè)間隔時(shí)間,然后計(jì)時(shí),達(dá)到預(yù)設(shè)的時(shí)間間隔之后才執(zhí)行邏輯;
在沒到達(dá)間隔時(shí)間這段時(shí)間內(nèi),如果有多次重復(fù)操作,那么后面的操作會(huì)頂替掉前面的計(jì)時(shí)任務(wù),重新開始計(jì)時(shí)。

應(yīng)用場景: 輸入框輸入內(nèi)容過程中去服務(wù)器查詢數(shù)據(jù),不能每次input都去查詢,所以只有當(dāng)用戶輸入停頓一定時(shí)間間隔時(shí),認(rèn)為用戶希望獲得查詢結(jié)果。

代碼如下:

/**
 * 防抖: 重復(fù)操作重置定時(shí)器
 */
function debounce (callback, delay, immediate) {
  let timer, context, args, isExecuted;

  let run = () => {
    timer = setTimeout(() => {
      !isExecuted && callback.apply(context, args);
      clearTimeout(timer);
      timer = null;
    }, delay)
  }

  return function() {
    context = this;
    args = arguments;
    // 重復(fù)進(jìn)入時(shí),設(shè)置為false,用于 run內(nèi)部進(jìn)行判斷
    // 若沒有此標(biāo)志位,run內(nèi)只能用 immediate 來判斷優(yōu)化
    // 但問題是,如果使用immediate,那么定時(shí)器完成后始終不會(huì)執(zhí)行callback
    isExecuted = false;

    // 定時(shí)器存在時(shí),清空并重建
    // 沒有必要釋放timer,因?yàn)閞un方法會(huì)重新賦值
    if (timer) {
      clearTimeout(timer);
      run()
    } else {
      // timer不存在的兩種情況
      // 1. 初始狀態(tài),還沒有創(chuàng)建任何定時(shí)器
      // 2. 完成了一次執(zhí)行,timer被釋放
      // 所以當(dāng)timer不存在時(shí),需要?jiǎng)?chuàng)建;
      // timer存在時(shí),說明還在進(jìn)行計(jì)時(shí),此時(shí)需要清除定時(shí)器并重新創(chuàng)建
      !!immediate && callback.apply(context, args);
      isExecuted = true; // 標(biāo)記為已執(zhí)行
      run();
    }
  }
}

測試代碼:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>節(jié)流Throttle</title>
  <style>
    #container {
      width: 200px;
      height: 200px;
      text-align: center;
      line-height: 200px;
      background-color: gray;
      color: white;
    }
  </style>
</head>
<body>
  <input id="input" type="text">
  <span>input輸入結(jié)果</span>
  <script>
    window.onload = () => {
      var inputEl = document.getElementById('input');

      inputEl.addEventListener('input', debounce(function(e) {
        this.nextElementSibling.textContent = this.value
      }, 1000, true));
    }

    /**
     * 防抖: 重復(fù)操作重置定時(shí)器
     */
    function debounce (callback, delay, immediate) {
      let timer, context, args, isExecuted;

      let run = () => {
        timer = setTimeout(() => {
          !isExecuted && callback.apply(context, args);
          clearTimeout(timer);
          timer = null;
        }, delay)
      }

      return function() {
        context = this;
        args = arguments;
        // 重復(fù)進(jìn)入時(shí),設(shè)置為false,用于 run內(nèi)部進(jìn)行判斷
        // 若沒有此標(biāo)志位,run內(nèi)只能用 immediate 來判斷優(yōu)化
        // 但問題是,如果使用immediate,那么定時(shí)器完成后始終不會(huì)執(zhí)行callback
        isExecuted = false;

        // 定時(shí)器存在時(shí),清空并重建
        // 沒有必要釋放timer,因?yàn)閞un方法會(huì)重新賦值
        if (timer) {
          clearTimeout(timer);
          run()
        } else {
          // timer不存在的兩種情況
          // 1. 初始狀態(tài),還沒有創(chuàng)建任何定時(shí)器
          // 2. 完成了一次執(zhí)行,timer被釋放
          // 所以當(dāng)timer不存在時(shí),需要?jiǎng)?chuàng)建;
          // timer存在時(shí),說明還在進(jìn)行計(jì)時(shí),此時(shí)需要清除定時(shí)器并重新創(chuàng)建
          !!immediate && callback.apply(context, args);
          isExecuted = true; // 標(biāo)記為已執(zhí)行
          run();
        }
      }
    }

  </script>
</body>
</html>

節(jié)流

當(dāng)短時(shí)間內(nèi)重復(fù)執(zhí)行某項(xiàng)操作時(shí),予以忽略,只執(zhí)行一次;知道執(zhí)行完成之后才會(huì)重新添加執(zhí)行能力。

實(shí)現(xiàn)原理: 維護(hù)一個(gè)定時(shí)器,每次執(zhí)行操作是都判斷定時(shí)器是否存在,如果定時(shí)器存在,直接return;
如果定時(shí)器不存在,則創(chuàng)建定時(shí)器,定時(shí)器到期后執(zhí)行,并清除定時(shí)器和定時(shí)器標(biāo)志

代碼如下:

const throttle = (callback, delay, immediate) => {
  let timer, context, args;

  let run = () => {
    timer = setTimeout(() => {
      // 僅在不是立即模式時(shí)執(zhí)行,防止二次執(zhí)行
      // 當(dāng) immediate 為 true時(shí), run方法的作用僅用于創(chuàng)建定時(shí)器,用于下次執(zhí)行控制
      if (!immediate) callback.apply(context, args)

      clearTimeout(timer) // 清除定時(shí)器,
      timer = null // 回收timer,防止對(duì)后面的執(zhí)行造成影響
    }, delay)
  }

  return function() {
    // 存儲(chǔ)上下文和實(shí)參列表
    context = this;
    args = arguments;

    // 當(dāng)前如果有定時(shí)器任務(wù),則取消操作
    if (timer) return

    // 如果需要立即執(zhí)行,則執(zhí)行
    if (immediate) callback.apply(context, arguments)
    // 再次執(zhí)行run方法創(chuàng)建定時(shí)器用于下次判斷
    run()
  }
}

測試:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>節(jié)流Throttle</title>
  <style>
    #container {
      width: 200px;
      height: 200px;
      text-align: center;
      line-height: 200px;
      background-color: gray;
      color: white;
    }
  </style>
</head>
<body>
  <div id="container"></div>
  <script>
    window.onload = () => {
      var containerEl = document.getElementById('container');
      var i = 0;
      containerEl.addEventListener('mousemove', throttle((e) => {
        containerEl.textContent = i++
      }, 200, true));
    }

    /**
     * 節(jié)流
     */
    const throttle = (callback, delay, immediate) => {
      let timer, context, args;

      let run = () => {
        timer = setTimeout(() => {
          // 僅在不是立即模式時(shí)執(zhí)行,防止二次執(zhí)行
          // 當(dāng) immediate 為 true時(shí), run方法的作用僅用于創(chuàng)建定時(shí)器,用于下次執(zhí)行控制
          !immediate && callback.apply(context, args);
          clearTimeout(timer) // 清除定時(shí)器,
          timer = null // 回收timer,防止對(duì)后面的執(zhí)行造成影響
        }, delay)
      }

      return function() {
        // 存儲(chǔ)上下文和實(shí)參列表
        context = this;
        args = arguments;
        
        if (timer) return; // 當(dāng)前如果有定時(shí)器任務(wù),則取消操作

        !!immediate && callback.apply(context, args); // 如果需要立即執(zhí)行,則執(zhí)行
        run() // 再次執(zhí)行run方法創(chuàng)建定時(shí)器用于下次判斷
      }
    }
  </script>
</body>
</html>

完整代碼

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>節(jié)流Throttle</title>
  <style>
    #container {
      width: 200px;
      height: 200px;
      text-align: center;
      line-height: 200px;
      background-color: gray;
      color: white;
      margin-top: 20px;
    }
  </style>
</head>
<body>
  <input id="input" type="text">
  <span>input輸入結(jié)果</span>
  <div id="container"></div>
  <script>
    window.onload = () => {
      var inputEl = document.getElementById('input');
      var containerEl = document.getElementById('container');
      var i = 0;

      inputEl.addEventListener('input', debounce(function(e) {
        this.nextElementSibling.textContent = this.value
      }, 1000, true));

      containerEl.addEventListener('mousemove', throttle((e) => {
        containerEl.textContent = i++
      }, 200, true));
    }

    /**
     * 防抖: 重復(fù)操作重置定時(shí)器
     */
    function debounce (callback, delay, immediate) {
      let timer, context, args, isExecuted;

      let run = () => {
        timer = setTimeout(() => {
          !isExecuted && callback.apply(context, args);
          clearTimeout(timer);
          timer = null;
        }, delay)
      }

      return function() {
        context = this;
        args = arguments;
        // 重復(fù)進(jìn)入時(shí),設(shè)置為false,用于 run內(nèi)部進(jìn)行判斷
        // 若沒有此標(biāo)志位,run內(nèi)只能用 immediate 來判斷優(yōu)化
        // 但問題是,如果使用immediate,那么定時(shí)器完成后始終不會(huì)執(zhí)行callback
        isExecuted = false;

        // 定時(shí)器存在時(shí),清空并重建
        // 沒有必要釋放timer,因?yàn)閞un方法會(huì)重新賦值
        if (timer) {
          clearTimeout(timer);
          run()
        } else {
          // timer不存在的兩種情況
          // 1. 初始狀態(tài),還沒有創(chuàng)建任何定時(shí)器
          // 2. 完成了一次執(zhí)行,timer被釋放
          // 所以當(dāng)timer不存在時(shí),需要?jiǎng)?chuàng)建;
          // timer存在時(shí),說明還在進(jìn)行計(jì)時(shí),此時(shí)需要清除定時(shí)器并重新創(chuàng)建
          !!immediate && callback.apply(context, args);
          isExecuted = true; // 標(biāo)記為已執(zhí)行
          run();
        }
      }
    }

    /**
     * 節(jié)流
     */
    const throttle = (callback, delay, immediate) => {
      let timer, context, args;

      let run = () => {
        timer = setTimeout(() => {
          // 僅在不是立即模式時(shí)執(zhí)行,防止二次執(zhí)行
          // 當(dāng) immediate 為 true時(shí), run方法的作用僅用于創(chuàng)建定時(shí)器,用于下次執(zhí)行控制
          !immediate && callback.apply(context, args);
          clearTimeout(timer) // 清除定時(shí)器,
          timer = null // 回收timer,防止對(duì)后面的執(zhí)行造成影響
        }, delay)
      }

      return function() {
        // 存儲(chǔ)上下文和實(shí)參列表
        context = this;
        args = arguments;
        
        if (timer) return; // 當(dāng)前如果有定時(shí)器任務(wù),則取消操作

        !!immediate && callback.apply(context, args); // 如果需要立即執(zhí)行,則執(zhí)行
        run() // 再次執(zhí)行run方法創(chuàng)建定時(shí)器用于下次判斷
      }
    }
  </script>
</body>
</html>
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本篇課題,或許早已是爛大街的解讀文章。不過春招系列面試下來,不少伙伴們還是似懂非懂地栽倒在(~面試官~)深意的笑容...
    以樂之名閱讀 1,864評(píng)論 0 9
  • 函數(shù)防抖(debounce) 防抖函數(shù) debounce 指的是某個(gè)函數(shù)在某段時(shí)間內(nèi),無論觸發(fā)了多少次回調(diào),都只執(zhí)...
    lacduang閱讀 271評(píng)論 0 0
  • 在進(jìn)行窗口的resize、scroll,輸入框內(nèi)容校驗(yàn)等操作時(shí),如果事件處理函數(shù)調(diào)用的頻率無限制,會(huì)加重瀏覽器的負(fù)...
    iqing2012閱讀 892評(píng)論 0 1
  • 作為一名前端開發(fā)者,我們經(jīng)常會(huì)處理各種事件,比如常見的click、scroll、 resize等等。仔細(xì)一想,會(huì)發(fā)...
    一米陽光kk閱讀 588評(píng)論 0 0
  • 久違的晴天,家長會(huì)。 家長大會(huì)開好到教室時(shí),離放學(xué)已經(jīng)沒多少時(shí)間了。班主任說已經(jīng)安排了三個(gè)家長分享經(jīng)驗(yàn)。 放學(xué)鈴聲...
    飄雪兒5閱讀 7,818評(píng)論 16 22

友情鏈接更多精彩內(nèi)容