1. ホーム
  2. javascript

[解決済み] JavaScriptによるシンプルなスロットル

2022-03-10 22:19:59

質問

JavaScriptで簡単なスロットルを探しています。lodashやunderscoreのようなライブラリがあることは知っていますが、1つの関数のためだけに、これらのライブラリのいずれかを含めることは過剰なことでしょう。

jQueryに同様の機能があるかどうかも調べてみましたが、見つかりませんでした。

1つだけ動作するスロットルを見つけました で、以下がそのコードです。

function throttle(fn, threshhold, scope) {
  threshhold || (threshhold = 250);
  var last,
      deferTimer;
  return function () {
    var context = scope || this;

    var now = +new Date,
        args = arguments;
    if (last && now < last + threshhold) {
      // hold on to it
      clearTimeout(deferTimer);
      deferTimer = setTimeout(function () {
        last = now;
        fn.apply(context, args);
      }, threshhold);
    } else {
      last = now;
      fn.apply(context, args);
    }
  };
}

この場合の問題は、スロットル時間が終了した後に、もう一回関数が起動することです。例えば、10秒ごとにキーを押すスロットルを作ったとすると、キーを2回押しても、10秒が経過した時点で2回目のキーが押されたことになります。このような動作はしたくありません。

解決方法は?

私なら アンダースコア.js または ローダッシュ のソースコードから、この関数のよくテストされたバージョンを見つけることができます。

underscoreのコードを少し修正し、underscore.js自体への参照をすべて取り除いたものがこちらです。

// Returns a function, that, when invoked, will only be triggered at most once
// during a given window of time. Normally, the throttled function will run
// as much as it can, without ever going more than once per `wait` duration;
// but if you'd like to disable the execution on the leading edge, pass
// `{leading: false}`. To disable execution on the trailing edge, ditto.
function throttle(func, wait, options) {
  var context, args, result;
  var timeout = null;
  var previous = 0;
  if (!options) options = {};
  var later = function() {
    previous = options.leading === false ? 0 : Date.now();
    timeout = null;
    result = func.apply(context, args);
    if (!timeout) context = args = null;
  };
  return function() {
    var now = Date.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;
  };
};

アンダースコアがサポートするすべてのオプションが必要ない場合は、このコードを簡略化することができることに注意してください。

この関数の非常にシンプルで設定不要なバージョンを以下にご紹介します。

function throttle (callback, limit) {
    var waiting = false;                      // Initially, we're not waiting
    return function () {                      // We return a throttled function
        if (!waiting) {                       // If we're not waiting
            callback.apply(this, arguments);  // Execute users function
            waiting = true;                   // Prevent future invocations
            setTimeout(function () {          // After a period of time
                waiting = false;              // And allow future invocations
            }, limit);
        }
    }
}

Edit 1: アンダースコアへの参照を削除しました。

Edit 2: ロダッシュとコードの簡略化についての提案を追加しました、lolzery @wowzery さんのコメントに感謝します。

Edit 3: ご要望が多かったので、@vsync のコメントを参考に、非常にシンプルで設定不要なバージョンの関数を追加しました。