こんにちは、さるまりんです 🐒🔧
JavaScriptを書いていると、
- 検索フォーム
- スクロール処理
- resizeイベント
- ボタン連打防止
などで、
「イベントが何回も呼ばれて困る」
という場面に出会うことがあります。
そこでよく出てくるのが、
- debounce
- throttle
です。
でも、
- 聞いたことあるんだけど違いが曖昧
- とりあえずコピペしている
- AIに生成してもらったけどよくわかっていない
という状態になりやすい部分でもあります。
なので今回は、
「なぜ必要なのか?」
から、実際に動かしながら整理してみます。
■ まずは「壊してみる」
まずは debounce も throttle も使わず、
イベントをそのまま処理してみます。
以下を index.html として保存して、
ブラウザで開いてみます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>resize test</title>
</head>
<body>
<h1>resize test</h1>
<script>
window.addEventListener('resize', () => {
console.log('resize');
});
</script>
</body>
</html>
ブラウザを開いた状態で、
ウィンドウサイズを少し動かしてみます。
すると…
開発者ツールで console を見てみると、
resize
resize
resize
resize
resize
resize
resize
ものすごい勢いで実行されます。
ほんの少し動かしただけでも、
大量にイベントが発生しています。
■ イベントは想像以上に大量発生する
ここがかなり重要です。
JavaScriptのイベントは、
「1回操作したら1回動く」
とは限りません。
例えば:
| イベント | 大量発生しやすい |
|---|---|
| scroll | かなり多い |
| resize | かなり多い |
| input | タイピングごと |
| mousemove | 非常に多い |
つまり、
console.log('動いた');
程度なら問題なくても、
- API通信
- DB検索
- 重い描画
- ファイル処理
などを直接書くと、
一気に危険になります。
■ debounce とは?
debounce は、
「最後の1回だけ実行する」
仕組みです。
例えば検索フォーム。
ユーザーが:
c
ca
cat
と入力した時に、
毎回API通信すると無駄が多くなります。
本当に欲しいのは、
cat
まで入力が終わった後ですよね。
そこで debounce を使います。
■ debounce を試してみる
以下を試してみます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>debounce test</title>
</head>
<body>
<input type="text" id="search" placeholder="入力してください">
<script>
function debounce(func, wait) {
let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => {
func.apply(this, args);
}, wait);
};
}
const searchInput = document.getElementById('search');
searchInput.addEventListener(
'input',
debounce((event) => {
console.log('検索:', event.target.value);
}, 500)
);
</script>
</body>
</html>
入力してみると、
タイプ中は動かず、
少し止まったタイミングで実行されます。
つまり:
入力中 → 待つ → 実行
です。
■ debounce が向いているケース
例えば:
| 用途 | 相性 |
|---|---|
| 検索フォーム | ◎ |
| オートコンプリート | ◎ |
| API検索 | ◎ |
| 入力補助 | ◎ |
逆に、
「リアルタイム性」
が欲しい場合には向きません。
■ throttle とは?
throttle は debounce と少し違います。
こちらは、
「一定間隔でだけ実行する」
仕組みです。
例えば scroll。
スクロール中ずっと止まるまで待っていたら、
画面更新が遅れてしまいます。
なので:
100msごとに実行
のように、
回数を間引きます。
■ throttle を試してみる
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>throttle test</title>
<style>
body {
height: 3000px;
}
</style>
</head>
<body>
<h1>scrollしてください</h1>
<script>
function throttle(func, limit) {
let lastExecuted = 0;
return function (...args) {
const now = Date.now();
if (now - lastExecuted >= limit) {
lastExecuted = now;
func.apply(this, args);
}
};
}
window.addEventListener(
'scroll',
throttle(() => {
console.log('scroll');
}, 300)
);
</script>
</body>
</html>
スクロールしてみると、
連続ではなく、
一定間隔でだけ実行されます。
■ debounce と throttle の違い
| 機能 | debounce | throttle |
|---|---|---|
| 動き | 最後だけ実行 | 間引きながら実行 |
| 向いている用途 | 検索 | scroll |
| リアルタイム性 | 低い | 高い |
| API保護 | 強い | 中程度 |
■ 実務では「負荷」より「体験」が大事なこともある
この記事では負荷の話を多めにしましたが、
実際にはUXのために使うことも多いです。
例えば:
- スクロールが重い
- 入力中に画面がカクつく
- ボタン連打で二重送信される
こういう問題は、
「サーバーが壊れる」
より先に、
「使いにくい」
として現れます。
つまり debounce / throttle は、
単なる最適化ではなく、
「気持ちよく動かすための設計」
でもあります。
どこにどんなテクニックを使うか、
とても大切ですよね。
■ AI時代だからこそ理解しておきたい
今は AI に、
debounce 関数を書いて
と頼めば、
簡単にコードを出してくれます。
でも、
- なぜ必要なのか
- debounce と throttle の違い
- どちらを選ぶべきか
が分からないと、
本当に必要な場面で使えません。
逆に、
考え方を理解していると、
AIが生成したコードも読みやすくなります。
AIにどっちがいいか提案してもらうのもいいでしょうし、
違いをとことん説明してもらうこともできるかなと思います。
でも、やっぱり基本的なことを知っていることと、
何を実現したいかを知っているのは、
作っている本人ですよね。
■ まとめ
debounce と throttle は、
どちらも
「イベントをそのまま流さない」
ための仕組みです。
ただ、
- 最後だけ実行したい → debounce
- 一定間隔で実行したい → throttle
という違いがあります。
JavaScriptでは、
イベントが想像以上に大量発生します。
だからこそ、
「動くコード」
だけではなく、
「どう制御するか」
も大切になってきますね。
読んでくださってありがとうございました。
それではまた!