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

JavaScript JavaScript
スポンサーリンク

Debounce 実装(簡易) — 入力が落ち着いたら一度だけ実行

「キー入力やスクロールなど“連打されるイベント”を落ち着いてから1回だけ処理したい」—その定番テクが debounce。短いコードで、無駄な処理やAPI呼び出しを抑えられます。


基本の実装と挙動

// 簡易 debounce 実装
const debounce = (fn, ms) => {
  let t;
  return (...args) => {
    clearTimeout(t);
    t = setTimeout(() => fn(...args), ms);
  };
};
JavaScript
  • 仕組み: 呼び出されるたびに前のタイマーを取消し、最後の呼び出しから ms 経過したら実行。
  • 効果: 高頻度イベントでも「最後の1回」だけが走る。APIや重い計算の呼び出し回数を削減。

例題:入力補完 API を呼ぶのは“手が止まってから”

// 入力が止まって300ms経ったら検索する
const search = (q) => {
  console.log("API検索:", q);
  // fetch(`/api/search?q=${encodeURIComponent(q)}`) ...
};

const onInput = debounce(search, 300);

document.querySelector("#q").addEventListener("input", (e) => {
  onInput(e.target.value);
});
JavaScript
  • ラベル: 連続入力中は実行されず、最後のキー入力から300ms待って1回だけ検索。

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

スクロール末尾で処理

const handleScrollEnd = debounce(() => {
  console.log("スクロールが一旦終了");
}, 200);
window.addEventListener("scroll", handleScrollEnd);
JavaScript

リサイズ完了時のみレイアウト再計算

const recalcLayout = debounce(() => {
  console.log("レイアウト再計算");
}, 250);
window.addEventListener("resize", recalcLayout);
JavaScript

フォームのオートセーブ(入力が途切れたら保存)

const autoSave = debounce((data) => {
  console.log("保存:", data);
  // save(data)
}, 500);

// 値の変化時に呼ぶ想定
autoSave({ title: "ドラフト", body: "..." });
JavaScript

実務でのコツと拡張版

this を保つ/キャンセル機能を付ける

function debounceEx(fn, ms = 300) {
  let t;
  const d = function (...args) {
    const ctx = this;
    clearTimeout(t);
    t = setTimeout(() => fn.apply(ctx, args), ms);
  };
  d.cancel = () => clearTimeout(t); // 途中で無効化
  return d;
}
JavaScript
  • ラベル: クラスやオブジェクトメソッドで使うなら apply で this を維持。不要になったら cancel()

先頭で一度だけ実行(leading)か末尾(trailing)か

function debounceAdv(fn, ms = 300, { leading = false } = {}) {
  let t, called = false;
  return (...args) => {
    if (leading && !called) {
      fn(...args);
      called = true;
    }
    clearTimeout(t);
    t = setTimeout(() => {
      if (!leading) fn(...args); // trailing
      called = false;            // ウィンドウ終了でリセット
    }, ms);
  };
}
JavaScript
  • ラベル: 先頭で一度(leading)か、最後だけ(trailing)かを選べると実用度アップ。

よくある落とし穴と対策

  • ラベル: 非同期を勘違い
    • Debounceは「遅らせる」だけ。先頭で即時実行したい場合は leading オプションを使う。
  • ラベル: 連打で永遠に実行されない
    • ms の間ずっと呼ばれ続けると末尾実行が来ない。UI的に必要なら leading を併用。
  • ラベル: this が失われる
    • メソッドに直接適用すると this が変わる。apply で明示的に束縛する拡張版を使う。
  • ラベル: 取り消し忘れ
    • ページ離脱やコンポーネント破棄時に未実行のタイマーが残る。cancel() を呼ぶか clearTimeout する。

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

// 1) 入力停止後500msでログ
const logInput = debounce((v) => console.log("入力:", v), 500);
// 連続で呼ぶ(実際は input イベントで)
logInput("a"); logInput("ab"); logInput("abc"); // → 最後の "abc" だけ出る

// 2) leading 一度+末尾なし(連打の最初だけ)
const clickOnce = debounceAdv(() => console.log("最初だけ"), 1000, { leading: true });
clickOnce(); clickOnce(); clickOnce(); // 最初の1回だけ出る(1秒経過でリセット)

// 3) メソッドの this を保つ
const counter = {
  n: 0,
  inc() { this.n++; console.log(this.n); }
};
counter.inc = debounceEx(counter.inc, 300);
counter.inc(); counter.inc(); counter.inc(); // → 1(最後の1回だけ、thisは維持)
JavaScript

直感的な指針

  • 高頻度イベントは“落ち着いたら1回”にするのが基本。
  • まずは簡易版で十分。this維持やleadingが必要なら拡張版にする。
  • 破棄時はキャンセルを忘れない。APIや計算は控えめに、UIはなめらかに。
タイトルとURLをコピーしました