JavaScript 逆引き集 | throttle 実装(簡易)

JavaScript JavaScript
スポンサーリンク

Throttle 実装(簡易) — 一定間隔だけ処理するスロットル

「スクロールやマウス移動のように、イベントが大量に連続発火して重くなる」問題を減らすテクが throttle(スロットル)。一定間隔ごとにしか処理を通さないことで、CPU負荷や無駄な処理を抑えます。


基本の実装と挙動

// 簡易 throttle 実装(最後に実行してからms経過するまで無視)
const throttle = (fn, ms) => {
  let last = 0;
  return (...args) => {
    const now = Date.now();
    if (now - last > ms) {
      last = now;
      fn(...args);
    }
  };
};
JavaScript
  • 仕組み: 前回実行時刻を記録し、指定ミリ秒以内の呼び出しはスキップ。間隔が空いたタイミングだけ実行。
  • 効果: 高頻度のイベントでも「最大毎 ms に1回」だけ処理されるため、重いUIや計算が安定。

例題:スクロール中の処理を間引く

const onScroll = throttle(() => {
  // 重い処理の代わりにログで挙動確認
  console.log("scroll handler");
}, 200);

window.addEventListener("scroll", onScroll);
JavaScript
  • 連続スクロールでも 200ms ごとに1回だけ実行。
  • ページの「カクつき」や「CPU過負荷」を抑えやすくなります。

すぐ使えるテンプレート集

マウス移動の位置表示を間引き

const showPos = throttle((e) => {
  console.log(e.clientX, e.clientY);
}, 100);

window.addEventListener("mousemove", showPos);
JavaScript

リサイズ中のレイアウト再計算を間引き

const recalc = throttle(() => {
  console.log("layout recalculated");
}, 250);

window.addEventListener("resize", recalc);
JavaScript

APIポーリングやスクロール位置計測などの負荷軽減

const measure = throttle(() => {
  const y = window.scrollY;
  // sendMetrics(y) など
  console.log("scrollY:", y);
}, 300);

window.addEventListener("scroll", measure);
JavaScript

実務でのコツと拡張版

this を保つ/最後の呼び出しを予約する(trailing)

function throttleEx(fn, ms = 200) {
  let last = 0, trailingTimer = null, lastArgs, lastCtx;
  return function (...args) {
    const now = Date.now();
    lastArgs = args; lastCtx = this;

    const run = () => { last = Date.now(); fn.apply(lastCtx, lastArgs); };

    if (now - last > ms) {
      run(); // すぐ実行
    } else {
      clearTimeout(trailingTimer);
      trailingTimer = setTimeout(run, ms - (now - last)); // 最後の一回を予約
    }
  };
}
JavaScript
  • 簡易版は「間隔内を捨てる」のに対し、拡張版は「最後の呼び出しを ms 経過後に実行」して取りこぼしを減らします。
  • applythis を保持できるため、オブジェクトメソッドでも安全。

leading/trailing の選択肢を持たせる

function throttleAdv(fn, ms = 200, { leading = true, trailing = true } = {}) {
  let last = 0, timer = null, lastArgs, lastCtx;

  const invoke = () => { last = Date.now(); fn.apply(lastCtx, lastArgs); };

  return function (...args) {
    const now = Date.now();
    lastArgs = args; lastCtx = this;

    if (last === 0 && !leading) {
      // 初回は捨てて、trailingだけに備える
      last = now;
    }

    const remaining = ms - (now - last);

    if (remaining <= 0) {
      clearTimeout(timer);
      timer = null;
      invoke(); // 直ちに実行
    } else if (trailing && !timer) {
      timer = setTimeout(() => {
        timer = null;
        invoke(); // 最後の呼び出しを後追いで一回
      }, remaining);
    }
  };
}
JavaScript
  • 先頭で即時実行するか(leading)、最後に一度実行するか(trailing)を用途に合わせて調整可能。

よくある落とし穴と対策

  • ラベル:処理の取りこぼし
    • 簡易版は間隔中の呼び出しを捨てるため、最後の値が反映されないことがある。
    • 対策: trailing ありの拡張版を使う。
  • ラベル:this の喪失
    • 直接 fn(...args) だとメソッドの this が変わる。
    • 対策: fn.apply(this, args) を使う拡張版。
  • ラベル:短すぎる間隔の設定
    • 10ms など極端に短いと意味が薄く、負荷が下がらない。
    • 対策: UIに見合う 100–300ms 程度から調整。
  • ラベル:用途の混同(debounceとの違い)
    • throttle は「一定ペースに制限」。debounce は「落ち着いたら一度」。
    • 対策: スクロール・mousemoveは throttle、入力確定やリサイズ終了検知は debounce。

練習問題(手を動かして覚える)

// 1) スクロールログを200ms間隔で出す(簡易版)
const logScroll = throttle(() => console.log("scroll"), 200);
window.addEventListener("scroll", logScroll);

// 2) 最後の呼び出しも拾う(trailingあり拡張版)
const logScrollTrailing = throttleEx(() => console.log("scroll (trailing)"), 200);
window.addEventListener("scroll", logScrollTrailing);

// 3) leading=false / trailing=true(最初は捨て、最後だけ)
const logMove = throttleAdv(() => console.log("mousemove end-ish"), 300, { leading: false, trailing: true });
window.addEventListener("mousemove", logMove);

// 4) メソッドで this を維持
const tracker = {
  n: 0,
  add() { this.n++; console.log(this.n); }
};
tracker.add = throttleEx(tracker.add, 150);
window.addEventListener("scroll", tracker.add);
JavaScript

直感的な指針

  • 高頻度イベントを「一定ペースに制限」したいなら throttle。
  • まずは簡易版で軽くする、取りこぼしが困るなら trailing 付きへ。
  • this を扱うなら apply の拡張版を使い、間隔は 100–300ms を目安に調整する。
タイトルとURLをコピーしました