スロットルデコレーター
「スロットリング」デコレーター`throttle(f, ms)`を作成します。これはラッパーを返します。
複数回呼び出された場合、`ms`ミリ秒あたり最大1回、`f`への呼び出しを渡します。
デバウンスデコレーターと比較して、動作は完全に異なります。
- `debounce`は「クールダウン」期間後に1回関数を実行します。最終結果を処理する場合に適しています。
- `throttle`は、指定された`ms`時間よりも頻繁に実行しません。非常に頻繁である必要のない定期的な更新に適しています。
言い換えれば、`throttle`は電話を受け付ける秘書のようなものであり、`ms`ミリ秒あたり1回よりも頻繁に上司(実際の`f`を呼び出す)を煩わせることはありません。
その要件をよりよく理解し、それがどこから来るのかを確認するために、現実世界のアプリケーションを確認しましょう。
たとえば、マウスの動きを追跡したいとします。
ブラウザでは、マウスの動きごとに実行され、移動中のポインタの位置を取得する関数を設定できます。アクティブなマウス使用中は、この関数は通常非常に頻繁に実行され、毎秒100回(10msごと)程度になる可能性があります。 **ポインタが移動したときにウェブページにいくつかの情報を更新したいと思います。**
…しかし、`update()`関数の更新は、微小な動きごとに実行するには重すぎます。また、100msより頻繁に更新する意味もありません。
そのため、デコレーターでラップします。元の`update()`の代わりに、各マウスムーブで実行する関数として`throttle(update, 100)`を使用します。デコレーターは頻繁に呼び出されますが、`update()`への呼び出しを最大100msに1回転送します。
視覚的には、次のようになります。
- 最初のマウス移動では、デコレートされたバリアントはすぐに`update`への呼び出しを渡します。これは重要です。ユーザーは、自分の移動に対する反応をすぐに確認できます。
- その後、マウスが移動し続け、`100ms`までは何も起こりません。デコレートされたバリアントは呼び出しを無視します。
- `100ms`の終わりに—最後の座標でさらに`update`が発生します。
- 最後に、マウスがどこかで停止します。デコレートされたバリアントは`100ms`が経過するまで待ってから、最後の座標で`update`を実行します。そのため、非常に重要な点として、最終的なマウス座標が処理されます。
コード例
function f(a) {
console.log(a);
}
// f1000 passes calls to f at maximum once per 1000 ms
let f1000 = throttle(f, 1000);
f1000(1); // shows 1
f1000(2); // (throttling, 1000ms not out yet)
f1000(3); // (throttling, 1000ms not out yet)
// when 1000 ms time out...
// ...outputs 3, intermediate value 2 was ignored
P.S. `f1000`に渡される引数とコンテキスト`this`は、元の`f`に渡される必要があります。
function throttle(func, ms) {
let isThrottled = false,
savedArgs,
savedThis;
function wrapper() {
if (isThrottled) { // (2)
savedArgs = arguments;
savedThis = this;
return;
}
isThrottled = true;
func.apply(this, arguments); // (1)
setTimeout(function() {
isThrottled = false; // (3)
if (savedArgs) {
wrapper.apply(savedThis, savedArgs);
savedArgs = savedThis = null;
}
}, ms);
}
return wrapper;
}
`throttle(func, ms)`への呼び出しは`wrapper`を返します。
- 最初の呼び出しでは、`wrapper`は`func`を実行し、クールダウン状態(`isThrottled = true`)を設定します。
- この状態では、すべての呼び出しが`savedArgs/savedThis`に記憶されます。コンテキストと引数の両方が同様に重要であり、記憶する必要があることに注意してください。呼び出しを再現するには、それらを同時に必要とします。
- `ms`ミリ秒が経過すると、`setTimeout`がトリガーされます。クールダウン状態は削除され(`isThrottled = false`)、呼び出しを無視した場合は、`wrapper`が最後に記憶された引数とコンテキストで実行されます。
第3ステップでは、`func`ではなく`wrapper`を実行します。`func`を実行する必要があるだけでなく、クールダウン状態に再入し、それをリセットするためのタイムアウトを設定する必要があるためです。