ホームページ  >  記事  >  ウェブフロントエンド  >  デバウンスとスロットリング

デバウンスとスロットリング

王林
王林オリジナル
2024-08-09 20:32:30444ブラウズ

Debouncing and Throttling

フロントエンドのインタビューでよくある質問の 1 つです。 JS、パフォーマンス、FE システム設計に関する面接者の知識をテストします。

これは、フロントエンドのインタビューの質問シリーズの質問 #2 です。準備をレベルアップしたい場合、または常に最新情報を入手したい場合は、FrontendCamp へのサインアップを検討してください。


デバウンスとスロットリングは同じ原理 (遅延に関するもの) に基づいて機能しますが、それでもアプローチと使用例が大きく異なります。

どちらの概念も、パフォーマンスの高いアプリケーションを開発するのに役立ちます。 あなたが毎日アクセスするほぼすべての Web サイトでは、デバウンシングとスロットリングが何らかの形で使用されています。

デバウンス

デバウンスのよく知られた使用例は、先行入力 (またはオートコンプリート) です。

何千もの商品を扱う電子商取引 Web サイトの検索機能を構築していると想像してください。ユーザーが何かを検索しようとすると、アプリは API 呼び出しを行って、ユーザーのクエリ文字列に一致するすべての商品を取得します。

const handleKeyDown = async (e) => {
 const { value } = e.target;
 const result = await search(value);
 // set the result to a state and then render on UI
}

<Input onKeyDown={handleKeyDown} />

このアプローチは問題ないようですが、いくつか問題があります:

  1. キーを押すイベントごとに API 呼び出しを行っています。ユーザーが 15 文字を入力すると、1 人のユーザーに対して 15 回の API 呼び出しが行われることになります。これでは決して拡張できません。
  2. これら 15 回の API 呼び出しの結果が到着したら、必要なのは最後の API 呼び出しだけです。過去 14 回の呼び出しの結果は破棄されます。ユーザーの帯域幅を大量に消費するため、低速ネットワークを使用しているユーザーには大幅な遅延が発生します。
  3. UI では、これらの 15 の API 呼び出しにより再レンダリングがトリガーされます。コンポーネントの動作が遅くなります。

これらの問題の解決策はデバウンスです。

基本的な考え方は、ユーザーが入力をやめるまで待つことです。 API 呼び出しを遅らせます。

const debounce = (fn, delay) => {
 let timerId;
 return function(...args) {
  const context = this;

  if (timerId) {
    clearTimeout(timerId);
  };
  timerId = setTimeout(() => fn.call(context, ...args), delay);
 }
}

const handleKeyDown = async (e) => {
 const { value } = e.target;
 const result = await search(value);
 // set the result to a state and then render on UI
}

<Input onKeyDown={debounce(handleKeyDown, 500)} />

デバウンスを利用できるように既存のコードを拡張しました。

デバウンス関数は、2 つの引数を取る汎用ユーティリティ関数です。

  1. fn: 遅延する関数呼び出し。
  2. 遅延: ミリ秒単位の遅延。

関数内では、setTimeout を使用して実際の function(fn) 呼び出しを遅延させます。タイマーが切れる前に fn が再度呼び出される場合、タイマーはリセットされます。

更新された実装では、ユーザーが 15 文字を入力した場合でも、API 呼び出しは 1 回だけ行われます (キーを押すたびにかかる時間が 500 ミリ秒未満であると仮定します)。これにより、この機能の構築を開始したときに発生したすべての問題が解決されます。

実稼働コードベースでは、独自のデバウンス ユーティリティ関数をコーディングする必要はありません。あなたの会社では、これらのメソッドを備えた lodash のような JS ユーティリティ ライブラリをすでに使用している可能性があります。

スロットリング

デバウンスはパフォーマンスに優れていますが、変更が通知されるまで x 秒間待ちたくないシナリオもいくつかあります。

Google ドキュメントや Figma のような共同ワークスペースを構築していると想像してください。重要な機能の 1 つは、ユーザーが他のユーザーに加えられた変更をリアルタイムで認識できることです。

これまでのところ、私たちが知っているアプローチは 2 つだけです。

  1. Noob のアプローチ: ユーザーがマウス ポインターを移動するか、何かを入力するたびに、API 呼び出しを行います。それがどれほどひどいことになるかはすでにご存知でしょう。
  2. デバウンスのアプローチ: パフォーマンス面は確かに解決しますが、UX の観点から見るとひどいものです。同僚が 300 ワードの段落を書いても、最終的に通知が届くのは 1 回だけかもしれません。それはまだリアルタイムとみなされますか?

ここでスロットリングが登場します。これは、上記の 2 つのアプローチのちょうど真ん中にあります。基本的な考え方は、定期的な間隔で通知することです。最後に通知したり、キーを押すたびに通知したりするのではなく、定期的に通知します。

const throttle = (fn, time) => {
 let lastCalledAt = 0;

 return function(...args) {
  const context = this;
  const now = Date.now();
  const remainingTime = time - (now - lastCalledAt);

  if (remainingTime <= 0) {
   fn.call(context, ...args);
   lastCalledAt = now;
  }
 }
}

const handleKeyDown = async (e) => {
 const { value } = e.target;
 // save it DB and also notify other peers
 await save(value);
}

<Editor onKeyDown={throttle(handleKeyDown, 1000)} />

スロットル関数を利用するために既存のコードを変更しました。 2 つの引数を取ります:

  1. fn: スロットルする実際の関数。
  2. time: 関数の実行が許可される間隔。

実装は簡単です。関数が最後に呼び出された時刻を lastCalledAt に保存します。次回、関数呼び出しが行われるときに、時間が経過したかどうかを確認し、それから初めて fn を実行します。

ほぼ完成に近づいていますが、この実装にはバグがあります。あるデータを含む最後の関数呼び出しが時間間隔内に行われ、その後呼び出しが行われなかった場合はどうなるでしょうか。現在の実装では、一部のデータが失われます。

これを修正するには、引数を別の変数に保存し、イベントが受信されなかった場合に後で呼び出されるタイムアウトを開始します。

const throttle = (fn, time) => {
 let lastCalledAt = 0;
 let lastArgs = null;
 let timeoutId = null;

 return function(...args) {
  const context = this;
  const now = Date.now();
  const remainingTime = time - (now - lastCalledAt);

  if (remainingTime <= 0) {
   // call immediately
   fn.call(context, ...args);
   lastCalledAt = now;
   if (timeoutId) {
     clearTimeout(timeoutId);
     timeoutId = null;
   }
  } else {
    // call later if no event is received
    lastArgs = args;
    if (!timeoutId) {
      timeoutId = setTimeout(() => {
        fn.call(context, ...lastArgs);
        lastCalledAt = Date.now();
        lastArgs = null;
        timeoutId = null;
      }, remainingTime);
    }
  }
 }
}

この更新された実装により、データを見逃すことがなくなります。

Lodash はスロットル ユーティリティ機能も提供します。


まとめ

  1. デバウンスとスロットリングはパフォーマンスを最適化する手法です。
  2. これらはどちらも同様の原理で機能し、物事を遅らせます。
  3. デバウンスは最後のイベントを受信した後 t 待機しますが、スロットリングは fn を t 時間内に定期的に実行します。
  4. デバウンスは検索機能で使用され、スロットリングはリアルタイム アプリで使用されます (これらに限定されません)。

リソース

フロントエンドキャンプ
ロダッシュ

以上がデバウンスとスロットリングの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。