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);
JavaScriptAPIポーリングやスクロール位置計測などの負荷軽減
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 経過後に実行」して取りこぼしを減らします。
applyでthisを保持できるため、オブジェクトメソッドでも安全。
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 を目安に調整する。
