最近在查前端面試常被問的問題時,發現了這兩個很常被問到的函數設計,但在這之前我完全沒聽過這兩個名詞😶‍🌫️,如果被問到可能真的會很抖!!!近一步查了才知道已經有實際寫過類似的行為,但完全沒有跟這個名詞連結起來。這篇就讓我們一起來看看什麼是防抖與節流,以及他們分別會運用在什麼場景裡吧!

為什麼需要防抖與節流?

防抖與節流的目的都是為了「提高網站的效能」,透過減少對 API 的請求次數,提高效能並改善用戶體驗。直接舉一個最簡單的例子,想必前端工程師們一定都有設計過輸入框(Input)這樣的欄位,當我們監聽 Input 的 OnChange 變化時會發現,當用戶想輸入 Coding 這個單字,每次輸入欄位都會監聽到最新的變化,在 console 印出來會看到 “C, Co, Cod, Codi, Coding”。

試想想,如果今天我們是做一個編輯器,那每輸一個字元就打一次 API 去儲存資料,當完成一個 “Coding” 就會連打六次,如果用戶手抖一直打錯…那打 API 的次數又會再增加,這樣是不是會很耗能呢?

防抖與節流是什麼?

一張圖比較防抖與節流 👇

Debounce&Throttle

1. 防抖(Debounce)

「當觸發停止後,直到過了某段時間函數才會執行。」

防抖的原理是通過延遲函數調用一定的時間。如果在這段時間內沒有任何事情發生,那麼函數就會執行,但如果在延遲期間有某些事情導致函數再次被調用,那麼延遲將重新開始計算。也就是説一個防抖的函數只會在它「最後一次被觸發後的一段特定延遲後運行一次」。

(1). 實際場景:搜尋欄位、手機號碼、信箱驗證

(2). 以搜尋欄位作為範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// debounce 函式,當在指定的延遲時間內沒有再次呼叫,它將執行 callback function
const debounce = (callback, delay = 250) => {
let timeout;

return (...args) => {
clearTimeout(timeout); // 清除先前的計時器,以確保 callback 只在延遲時間後調用一次
timeout = setTimeout(() => {
callback(...args);
}, delay);
};
}

// 用於更新搜尋結果的函式
const updateSearchResults = debounce(query => {
fetch(`/api/search?query=${query}`)
.then(res => res.json())
.then(data => displayResults(data)) // 假設 displayResults 會顯示搜尋結果到 UI
.catch(error => console.error("Error fetching search results:", error));
}, 500); // 這裡選擇 500 毫秒作為延遲,代表用戶輸入停止超過 500 毫秒後才發出請求

// 監聽搜尋框的 input 事件
const searchInput = document.querySelector("#searchInput");
searchInput.addEventListener("input", e => {
updateSearchResults(e.target.value);
});

在上面的例子中,當用戶在搜尋框 (#searchInput) 中輸入文字時,不會立即觸發 updateSearchResults。只有當用戶停止輸入超過 500 毫秒,才會發送搜尋請求到伺服器。

2. 節流(Throttle)

「在一段時間內,確保函數的執行次數不超過某個次數。」

不同於防抖,節流是在固定一段時間內執行一次。例如,如果我們的延遲設置為 1 秒,即使你在 1 秒內連續調用它,它也只會執行一次。

(1). 實際場景:滾動事件、滑鼠移動、鍵盤點擊等頻繁觸發情境

(2). 以滾動事件作為範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
const throttle = (callback, delay = 1000) => {
let shouldWait = false; // 表示是否在延遲時間內
let waitingArgs; // 用於存儲在延遲時間內的最後一次調用的參數

const timeoutFunc = () => {
if (waitingArgs == null) {
shouldWait = false;
} else {
callback(...waitingArgs);
waitingArgs = null;
setTimeout(timeoutFunc, delay);
}
};

return (...args) => {
if (shouldWait) {
waitingArgs = args;
return;
}

callback(...args);
shouldWait = true;
setTimeout(timeoutFunc, delay);
}
}

const scrollHandler = event => {
console.log("Handling scroll");
// 執行滾動事件觸發內容
}

const throttledScrollHandler = throttle(scrollHandler, 250);

window.addEventListener("scroll", throttledScrollHandler);

上述這個 throttle 函式不僅可以對第一次事件作出反應,還可以對最後一次事件作出反應。例如當你需要確保某些操作(例如動畫或渲染)在一系列連續事件(例如滾動)結束後才被執行,這一個函式就很實用。

以上就是關於防抖與節流的目的以及實際的函式設計,希望可以幫助大家理解這兩個情境並試著在前端的網頁邏輯中實踐它!


💬 參考資料:

  1. How To Implement Debounce And Throttle In JavaScript:這篇文章也是目前為止我看到解釋最完整也最好理解的,還附有影片教學~非常推薦大家去看看。
  2. JavaScript中的函数节流与防抖
  3. JavaScript 防抖(debounce)与节流(throttle)