フロントエンドのインタビューでよくある質問の 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} />
このアプローチは問題ないようですが、いくつか問題があります:
これらの問題の解決策はデバウンスです。
基本的な考え方は、ユーザーが入力をやめるまで待つことです。 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 つの引数を取る汎用ユーティリティ関数です。
関数内では、setTimeout を使用して実際の function(fn) 呼び出しを遅延させます。タイマーが切れる前に fn が再度呼び出される場合、タイマーはリセットされます。
更新された実装では、ユーザーが 15 文字を入力した場合でも、API 呼び出しは 1 回だけ行われます (キーを押すたびにかかる時間が 500 ミリ秒未満であると仮定します)。これにより、この機能の構築を開始したときに発生したすべての問題が解決されます。
実稼働コードベースでは、独自のデバウンス ユーティリティ関数をコーディングする必要はありません。あなたの会社では、これらのメソッドを備えた lodash のような JS ユーティリティ ライブラリをすでに使用している可能性があります。
デバウンスはパフォーマンスに優れていますが、変更が通知されるまで x 秒間待ちたくないシナリオもいくつかあります。
Google ドキュメントや Figma のような共同ワークスペースを構築していると想像してください。重要な機能の 1 つは、ユーザーが他のユーザーに加えられた変更をリアルタイムで認識できることです。
これまでのところ、私たちが知っているアプローチは 2 つだけです。
ここでスロットリングが登場します。これは、上記の 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 つの引数を取ります:
実装は簡単です。関数が最後に呼び出された時刻を 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 はスロットル ユーティリティ機能も提供します。
フロントエンドキャンプ
ロダッシュ
以上がデバウンスとスロットリングの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。