首頁  >  文章  >  web前端  >  去抖和節流

去抖和節流

王林
王林原創
2024-08-09 20:32:30468瀏覽

Debouncing and Throttling

另一個流行的前端面試問題。它測試受訪者對 JS、性能和 FE 系統設計的知識。

這是前端面試問題系列的第二個問題。如果您希望提高準備程度或保持最新狀態,請考慮註冊 FrontendCamp。


去抖和節流的工作原理相同 - 延遲東西 - 但仍然有非常不同的方法和用例。

這兩個概念對於開發高效能應用程式都很有用。 您每天造訪的幾乎所有網站都以某種方式使用去抖和節流。

去抖

去抖動的一個眾所周知的用例是預先輸入(或自動完成)。

假設您正在為一個擁有數千種產品的電子商務網站建立搜尋功能。當使用者嘗試搜尋某些內容時,您的應用程式將進行 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 個字符,則單一使用者會呼叫 15 次 API。這永遠無法擴展。
  2. 當這 15 個 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)} />

我們擴展了現有程式碼以利用去抖動。

去抖動函數是通用實用函數,它接受兩個參數:

  1. fn:應該延遲的函數呼叫。
  2. 延遲:以毫秒為單位的延遲。

在函數內部,我們使用 setTimeout 來延遲實際的函數(fn)呼叫。如果在計時器耗盡之前再次呼叫 fn,計時器將重設。

透過我們更新的實現,即使使用者輸入 15 個字符,我們只會進行 1 次 API 呼叫(假設每次按鍵的時間少於 500 毫秒)。這解決了我們開始建立此功能時遇到的所有問題。

在生產程式碼庫中,您不必編寫自己的去抖動實用函數。您的公司很可能已經使用了像 lodash 這樣具有這些方法的 JS 實用程式庫。

節流

嗯,去抖動對於效能來說非常有用,但在某些情況下,我們不想在收到更改通知之前等待 x 秒。

想像一下您正在建立一個協作工作空間,例如 Google Docs 或 Figma。關鍵功能之一是用戶應該即時了解其他用戶所做的更改。

到目前為止我們只知道兩種方法:

  1. Noob 方法:每當使用者移動滑鼠指標或鍵入內容時,都會進行 API 呼叫。你已經知道事情會變得多糟。
  2. 去抖動方法:它確實解決了效能方面的問題,但從使用者體驗的角度來看,它很糟糕。您的同事可能會寫一個 300 字的段落,但您最終只會收到一次通知。還算實時嗎?

這就是節流的用武之地。它位於上述兩種方法的中間。基本想法是 - 定期通知 - 不是在最後也不是在每次按鍵時通知,而是定期通知。

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)} />

我們修改了現有程式碼以利用節流功能。它需要兩個參數:

  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. Debounce 在收到最後一個事件後等待 t,而 Throttling 在 t 時間內定期執行 fn。
  4. 去抖動用於搜尋功能,節流用於即時應用程式(不限於這些)。

資源

前端營
洛達什

以上是去抖和節流的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn