手動實現JS節流

原文地址: https://www.jeremyjone.com/705/,轉載請註明。


什麼是節流

在函數調用過程中,避免過於頻繁的調用,而是間隔某一時間後調用一次的方法,叫做節流。

節流做什麼

節流可以有效避免短時間內大量調用某一方法或數據,保證頁面的穩定性和數據的準確性。

一個小的例子

使用 underscore 的節流功能來測試一下效果。

中文網址

在頁面中直接導入 cdn 即可。

https://cdn.bootcss.com/underscore.js/1.9.1/underscore.js

未節流時的樣子

將下面內容粘貼到一個 HTML 的 body 標籤中。

<div
  id="container"
  style="width:100%;height:200px;line-height:200px;text-align:center;color:#fff;background-color:#444;font-size:30px;"
></div>
<script>
  let count = 0;
  let container = document.querySelector("#container");

  // 此處爲高頻調用函數
  function doSomething() {
    container.innerHTML = count++;
  }

  container.onmousemove = doSomething;
</script>

這段代碼會生成一個灰色框,只要鼠標在其內部移動,就會調用 doSomething 函數,導致數字不斷增長。

使用節流

同樣地,將下面內容粘貼到一個新的 HTML 的 body 標籤中。

<div
  id="container"
  style="width:100%;height:200px;line-height:200px;text-align:center;color:#fff;background-color:#444;font-size:30px;"
></div>
<script src="https://cdn.bootcss.com/underscore.js/1.9.1/underscore.js"></script>
<script>
  let count = 0;
  let container = document.querySelector("#container");

  // 此處爲高頻調用函數
  function doSomething() {
    container.innerHTML = count++;
  }

  container.onmousemove = _.throttle(doSomething, 300);
</script>

這裏導入了 underscore 庫,這個庫會導出一個 _ 的對象,它包含一個 throttle 方法,該方法的作用就是節流。第一個參數是函數原型,第二個參數是響應時間,這裏我們設置 300ms 後響應。

另外還可以設置是否第一次首先執行和最後一次是否執行,傳入: {leading: false}{trailing: false} 。當然,你也可以同時傳入,但是需要注意,如果同時傳入 false 的話,會出現 bug,該 bug 會導致下次啓動時不響應 {leading: false} ,而是會立即執行第一次,這一點需要注意。

這次運行後,可以發現當鼠標移動時,不再一味地增加,而是每隔一段時間後(300ms)纔會響應一次。

手動實現節流函數

利用時間戳方式

這樣的方式會觸發第一次,而不會觸發最後一次。

function throttle(func, wait) {
  let context, args;
  let old = 0;
  return function () {
    context = this;
    args = arguments;
    let now = Date.now();

    if (now - old > wait) {
      // 立即執行
      func.apply(context, args);
      old = now;
    }
  };
}

利用定時器方式

這樣的方式會觸發最後一次,而不會觸發第一次。

function throttle(func, wait) {
  let context, args, timeout;

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

    // 設置一個定時器,只有爲空時纔會觸發,每次執行後都會重新設定一個定時器。
    if (!timeout) {
      timeout = setTimeout(() => {
        timeout = null;
        func.apply(context, args);
      }, wait);
    }
  };
}

合併爲最終的完整本版

利用上面的兩種實現方式,可以得到一個比較完整的版本。

function throttle(func, wait, options) {
  let context, args, timeout, result;
  let previous = 0;

  if (!options) options = {};

  let later = function () {
    // 這裏控制再次出發時,第一次是否執行,當爲雙false時,也是這裏會出現的問題。
    previous = options.leading === false ? 0 : Date.now();
    timeout = null;
    result = func.apply(context, args);
    if (!timeout) context = args = null;
  };

  var throttled = function () {
    context = this;
    args = arguments;

    let now = Date.now();

    // 第一次不執行,調整previous的值即可。
    if (!previous && options.leading === false) previous = now;

    if (now - previous > 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, wait);
    }

    return result;
  };

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

  return throttled;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章