---
# System prepended metadata

title: Debounce 概念由來與程式範例
tags: [JavaScript]

---

# Debounce 概念由來與程式範例

:::info
Debounce（防彈跳）是來自電子工程領域的用詞，是一種**避免高頻率、重複性執行事件**的技術，在軟體中主要用於改善效能和減輕系統壓力。

由於在電路學中 Debounce Circuit 常翻譯成「**防彈跳電路**」，因此本文統一稱 debounce 作「防彈跳」。
:::

## 問題點：Autocomplete 過度頻繁觸發 API

在開發搜尋框功能時，常常會伴隨自動補齊（Autocomplete）功能，即使用者輸入資料時需要查詢資料庫中對應的資料，並顯示在畫面上。類似以下畫面：

![image](https://hackmd.io/_uploads/SJxyA82Reg.png)

然而，如果設計不良，會導致使用者異動資料時大量發送查詢，對前後端造成不必要的計算負擔。因此，我們需要使用 Debounce 的技術，確保只在使用者輸入停頓後才執行最終的搜尋。

![Frame 28](https://hackmd.io/_uploads/SJeZHDhAgx.png)

## 什麼是 Debounce 呢？來自電子工程的啟示

Debounce 的概念源於電子工程（Electrical Engineering）領域，用來解決機械開關的訊號雜訊問題，後被廣泛應用於電腦科學（Computer Science）。

當實體機械開關的導體接觸或分離時，金屬的彈性和慣性會造成短暫的 **彈跳（Bouncing）** 現象，導致訊號不穩定。Debounce 的目的，就是在初始彈跳期內忽略所有不穩定的訊號，只接受開關完全穩定後的最終狀態。

> ![image](https://hackmd.io/_uploads/SkE59920gg.png)
> 示波器快照顯示，開關在開啟和關閉之間反覆跳動，然後穩定下來。圖片取自[英文維基百科](https://en.wikipedia.org/wiki/Switch#Contact_bounce)。


我們透過兩個生活案例來理解：

1. **遊戲手把**
當玩家只按了一次手把上的「跳躍」按鈕，如果沒有防彈跳機制，遊戲程式可能因為金屬觸點的多次彈跳而接收到多個「按下」訊號。結果遊戲角色可能在瞬間連續跳躍。

2. **電視遙控器**
使用者按了一次「頻道 + 1」，若觸點發生彈跳，遙控器電路會發射多個「換台」指令，電視頻道可能會連跳好幾台。

![Frame 33 (1)](https://hackmd.io/_uploads/rJ-HXsn0lg.png)


要解決這個物理問題，有兩種方法：

1. **硬體防彈跳（Hardware Debounce）**
利用電阻－電容（RC） 網路等電子元件。電容的充/放電特性可以將快速變動的雜訊訊號平滑，確保只有穩定的電壓變化能傳遞。

> ![image](https://hackmd.io/_uploads/rk23T9nRex.png)
> 示意圖，[資料來源](https://www.picotech.com/library/articles/blog/what-is-switch-bounce-how-to-implement-debounce)


2. **軟體防彈跳（Software Debounce）**
利用程式碼解決。一旦偵測到開關狀態改變，程式會啟動一個延遲計時器。在計時器結束前，所有後續的訊號變化都將被忽略或重置。

## 概念移植到電腦科學

Debounce 這種「避免高頻率、重複性地執行事件」的概念，也被應用到軟體領域：
- 後端：在即時監控中，Debounce 可將短時間內重複發生的狀態變更事件延遲並合併，確保資料庫只執行一次最終更新，減輕寫入壓力。
- 前端：搜尋框 Autocomplete 過度頻繁觸發 API、或繪圖程式中使用者拖動滑塊調整參數時，Debounce 能確保複雜的畫面渲染或 API 呼叫只在使用者操作停止後執行一次。

### 程式概念

軟體 Debounce 必須依賴兩個關鍵步驟：
1. 設置一個**延遲計時器**來執行目標函式
2. 在延遲期間重複呼叫時，必須**取消前一個**未執行的計時器

```javascript!
// 定義 debounce 函式
function debounce(callback, delay) {
  let timer;
  return function debouncedCallback(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => {
      callback(...args);
    }, delay);
  };
}

// 定義要執行的動作
const printSomething = (msg) => console.log(msg);

// 建立 debounce 版本的函式
const debouncePrintSomething = debounce(printSomething, 300);

// 實際操作時，執行 debounce 版本的函式
debouncePrintSomething("Hello"); // 會被取消
debouncePrintSomething("World"); // 只會在 300 毫秒後印出 "World"
```

### 核心機制：藉由閉包（Closure）持續追蹤 timer
Debounce 的關鍵在於確保所有調用都操作**同一個 `timer` 變數**，這正是透過**閉包**技術實現的：

當 `debounce()` 執行並返回 `debouncedCallback()` 時，`timer` 變數會被這個內部函式持續封存和依賴，形成了閉包，可以有以下好處：

- 共享狀態
無論 `debouncePrintSomething()` 被呼叫多少次，所有執行個體都指向並修改同一個被封存的 `timer` 變數。
這使得每一次 `clearTimeout()` 都能讀取到同一個 `timer`，而能精確地取消未完成的任務。

- 隔離保護
由於 `timer` 變數隔離在 `debounce()` 的作用域內，外部程式碼無法直接存取或意外修改它，確保了 `debounce()` 狀態的獨立性。

## 總結
Debounce 技術的本質是合併短時間頻繁的接觸／呼叫，在前端中，我們透過 `setTimeout` 延遲並以 `clearTimeout` 取消，減少不必要的資源浪費，從而提升整體使用者體驗。