文章

前端面試手寫練習 - debounce

文章發表於

問題

debounce 與 throttle 都是在前端開發中相當常見的一種優化手段,而 debounce 的概念就是當事件被觸發後,會延遲一段時間再執行,如果在這段時間內又被觸發,則會重新計算延遲時間。

debounce(func, [(wait = 0)])
  • func:要防抖動的事件函式
  • wait:延遲時間,單位為毫秒

示意圖

原始事件流 (每 ─ 代表 1s):
─ A ─ B ─ C ─ ─ D ─ ─ ─ ─ ─ ─ E ─ ─ F ─ G ─ ─ ─
加入 throttle (wait: 3s) 之後:
─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ D ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ G

可以看到加入 debounce 之後,只有事件 DG 被執行。

範例

實務上最經典的例子就是透過 debounce 來優化搜尋功能,當使用者輸入關鍵字時,並不會立即發送請求,而是等待一段時間,再發送下一個請求。

<!DOCTYPE html>
<html>

<head>
  <title>Parcel Sandbox</title>
  <meta charset="UTF-8" />
  <link rel="stylesheet" href="/styles.css" />
</head>

<body>
  <h1>Hello world</h1>
</body>

</html>

可以看到輸入關鍵字時,「累積請求發送次數」只有在停止輸入的一段時間後才會增加。亦即加入 debounce 後減少了對後端請求的發送次數,減緩了伺服器的負擔。

相反的,如果把在 keyup 時的 debounce 函式移除,則會在每次輸入時都會看到「累積請求發送次數」的次數增加。

練習區

在了解問題後,可以嘗試先寫下您的思路,再到下方的練習區域實際寫出程式碼。

import { add } from './add';

describe('add', () => {
  test('Commutative Law of Addition', () => {
    expect(add(1, 2)).toBe(add(2, 1));
  });
});

Open browser consoleTests

筆者思路

  1. 這與 throttle 的實作方式非常相近,但較於 throttle 是在 timeout 結束時將開關再次打開, debounce 則是在 timeout 結束時呼叫 func 並將 timer 設為 null
  2. 照著這個思路,我們可以先宣告一個 timer 變數,並在 debounce 函式中返回一個新的函式。
  3. 在這個新函式中,我們會先檢查 timer 是否存在,如果存在則清除計時器。
  4. 接著設定一個新的計時器,並在時間到時執行 func,並將 timer 設為 null

筆者解答

function debounce(func, wait = 0) {
let timer = null
return function (...args) {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
timer = null
func.apply(this, args)
}, wait)
}
}

相關題目

  1. bigfrontend.dev - basic debounce

延伸閱讀

  1. Debounce vs Throttle: Definitive Visual Guide
如果您喜歡這篇文章,請點擊下方按鈕分享給更多人,這將是對筆者創作的最大支持和鼓勵。