防抖と節流とは何か?
防抖(Debounce)と節流(Throttle)は、高頻度に発生するイベントを制御するための代表的な技術です。特に前端開発において、ユーザー操作によるリクエスト過多や処理負荷の問題を軽減するために使われます。
主な利用シーン
- ログインボタン:連続クリックによる複数リクエスト送信を防止。
- ウィンドウサイズ変更(resize):頻繁なイベントが発生する場合、最終状態での処理のみ実行。
- リアルタイム保存:入力停止後1秒後にデータを保存。
- スクロール位置の取得:1秒ごとに座標を更新。
- 検索フィルタ:入力毎にリクエストを送る際、一定間隔でしか送らないように制限。
両者の違い
| 項目 | 防抖(Debounce) | 節流(Throttle) |
|---|---|---|
| 動作原理 | イベント発生後、指定時間経過まで待機し、最後の一度だけ実行 | 指定間隔以上でしか実行されない |
| タイミング | 最後のイベントからカウント開始 | 最初のイベントからカウント開始 |
| 目的 | 「誤作動」防止(一時的な入力乱れを無視) | 処理頻度の抑制(サーバー負荷軽減) |
| 実装の鍵 | clearTimeoutでタイマーをリセット | タイマーの有効/無効切り替え(ロック機構) |
実装例
防抖関数の実装
function createDebounce(func, delay, immediate = false) {
let timeoutId;
return function (...args) {
const context = this;
const shouldCallNow = immediate && !timeoutId;
clearTimeout(timeoutId);
if (shouldCallNow) {
func.apply(context, args);
} else {
timeoutId = setTimeout(() => {
timeoutId = null;
func.apply(context, args);
}, delay);
}
};
}
節流関数の実装
function createThrottle(func, interval) {
let lastExecTime = 0;
let timeoutId = null;
return function (...args) {
const now = Date.now();
const context = this;
clearTimeout(timeoutId);
if (now - lastExecTime >= interval) {
func.apply(context, args);
lastExecTime = now;
} else {
timeoutId = setTimeout(() => {
func.apply(context, args);
lastExecTime = Date.now();
}, interval - (now - lastExecTime));
}
};
}
補足:言語機能の理解
ES6 Rest演算子(...)は、関数の引数を配列として受け取るための構文です。これによりargumentsオブジェクトの使用が不要になります。
const logArgs = (...values) => console.log(values);
logArgs(1, 2, 3); // [1, 2, 3]
apply()メソッドは、関数のコンテキスト(this)と引数リストを別々に渡すことができます。特に配列を関数の引数として展開する際に便利です。
const numbers = [1, 5, 3, 9];
Math.max.apply(null, numbers); // 9
// ES6以降では...演算子でより簡潔に
Math.max(...numbers); // 9
防抖と節流の実装では、...argsで引数を配列化し、apply()で関数に渡すことで、元の呼び出し環境を正確に再現しています。
これらの仕組みは、lodashやunderscoreなどのライブラリでも提供されており、自前実装よりも信頼性が高いです。