魯斯前端布魯斯前端

文章中英模式

常見的前端面試題目 - 用戶交互優化 - Throttle 與 Debounce

深入理解 JavaScript 中的 Throttle 節流與 Debounce 防抖技術,包含兩者差異、實作原理、適用場景詳解與實際範例,掌握前端效能優化與面試常考題目。

影片縮圖

懶得看文章?那就來看影片吧

什麼是 Throttle 與 Debounce?為什麼需要它們?

Throttle(節流)和 Debounce(防抖)是兩種常用的前端效能優化技術,用於控制高頻事件(如滾動、搜索、調整視窗大小)的執行頻率。

  • Throttle(節流):限制函式在一段時間內只執行一次,無論觸發多少次。像是「每隔 N 毫秒最多執行一次」。
  • Debounce(防抖):將多次連續觸發合併為一次,只在最後一次觸發後等待一段時間才執行。像是「等待 N 毫秒沒有新觸發才執行」。

這兩種技術對於優化前端效能、提升使用者體驗非常重要:

  • 減少不必要的函式呼叫
  • 降低 API 請求頻率
  • 優化資源密集型計算
  • 減少 DOM 操作頻率

Throttle(節流)實作與原理

節流的核心概念是「保證在指定時間內,函式最多只能執行一次」:

function throttle(func, delay) {
  let lastCall = 0;
  
  return function(...args) {
    const now = Date.now();
    if (now - lastCall >= delay) {
      func.apply(this, args);
      lastCall = now;
    }
  };
}

// 使用範例
const throttledScroll = throttle(() => {
  console.log('滾動位置:', window.scrollY);
}, 300);

window.addEventListener('scroll', throttledScroll);

圖解 Throttle 執行原理:

觸發事件:↓  ↓  ↓  ↓  ↓  ↓  ↓  ↓  ↓
執行函式:↓        ↓        ↓
時間軸:  |--------|--------|------->
          300ms    300ms    300ms

Debounce(防抖)實作與原理

防抖的核心概念是「等待指定時間內沒有新的呼叫再執行函式」:

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

// 使用範例
const searchInput = document.getElementById('search');
const debouncedSearch = debounce((e) => {
  console.log('搜尋關鍵字:', e.target.value);
  // fetchResults(e.target.value);
}, 500);

searchInput.addEventListener('input', debouncedSearch);

圖解 Debounce 執行原理:

觸發事件:↓  ↓  ↓      ↓ ↓          ↓
重設計時:⟳  ⟳  ⟳      ⟳ ⟳          ⟳
執行函式:                ✓            ✓
時間軸:  |-----|-----|-----|-----|---->
        等待中  等待中 執行   等待中 執行

使用場景比較

Throttle 與 Debounce 適用場景比較:

技術適用場景實際應用
Throttle(節流)滾動事件處理社交媒體的無限滾動加載,每300ms檢查一次滾動位置
遊戲中的按鍵控制射擊遊戲中限制玩家每秒最多發射5顆子彈
地圖拖動/縮放Google地圖拖動時,每100ms才重新請求地圖數據
Canvas 繪圖繪圖應用中,筆刷跟隨滑鼠以固定間隔繪製點
Debounce(防抖)搜尋框輸入Google搜尋框停止輸入500ms後才發送搜尋請求
表單驗證註冊表單中,用戶輸入完郵箱後才檢查是否已被使用
自動保存Google文件在用戶停止輸入2秒後自動保存內容
窗口大小調整響應式網站在用戶完成調整視窗大小後才重新排版

實際應用案例與效能差異

案例 1:滾動事件優化(使用 Throttle)

未優化前,滾動時可能會觸發上百次函式呼叫:

// 未優化 - 每次滾動都會執行
window.addEventListener('scroll', () => {
  loadMoreContent();
});

// 優化後 - 每 200ms 最多執行一次
window.addEventListener('scroll', throttle(() => {
  loadMoreContent();
}, 200));

案例 2:自動保存表單(使用 Debounce)

未優化前,輸入每個字都會觸發保存:

// 未優化 - 每次輸入都會保存
editor.addEventListener('input', saveContent);

// 優化後 - 停止輸入 500ms 後才保存
editor.addEventListener('input', debounce(saveContent, 500));

🔥 常見面試題目

(一)Throttle 和 Debounce 的主要區別是什麼?

解答:Throttle(節流)控制函式在一段時間內最多執行一次,無論觸發多少次,適合持續性事件如滾動。例如:在電商網站上,當使用者滾動頁面時,使用 Throttle 每 200ms 檢查一次是否需要載入更多商品,而不是每次滾動都觸發。Debounce(防抖)則是將多次連續觸發合併為一次,只在最後一次觸發後等待一段時間才執行,適合連續觸發但只需最終結果的場景如搜尋。例如:在搜尋框中,使用者快速輸入「React 教學」時,使用 Debounce 等待使用者停止輸入 500ms 後才發送 API 請求,而不是每打一個字就發送一次請求。

(二)以下程式碼的執行結果是什麼?

let count = 0;

const btn = document.getElementById('btn');
const throttledClick = throttle(() => {
  console.log(`點擊次數: ${++count}`);
}, 1000);

// 假設使用者在 2 秒內快速點擊 5 次
btn.addEventListener('click', throttledClick);
解答:假設使用者在 2 秒內點擊 5 次,console 會輸出:
  1. 第 0 秒時:「點擊次數: 1」(首次立即執行)
  2. 第 1 秒時:「點擊次數: 2」(間隔滿 1 秒後執行)
  3. 第 2 秒時:「點擊次數: 3」(間隔滿 1 秒後執行)
不會執行 5 次,而是按照 throttle 的時間間隔,每秒最多執行一次。

(三)以下 debounce 程式碼的執行結果是什麼?

let count = 0;

const searchInput = document.getElementById('search');
const debouncedSearch = debounce(() => {
  console.log(`搜尋次數: ${++count}`);
}, 500);

// 假設使用者在 1 秒內快速輸入 5 個字元
searchInput.addEventListener('input', debouncedSearch);
解答:假設使用者在 1 秒內快速輸入 5 個字元,console 會輸出:
  1. 第 1.5 秒時:「搜尋次數: 1」(最後一次輸入後 500ms 執行)
不會執行 5 次,而是按照 debounce 的原理,只在最後一次輸入事件後等待 500ms 才執行一次。如果使用者在計時器結束前又輸入了新字元,則重新計時。這就是為什麼 debounce 適合用於搜尋框等需要等待使用者停止輸入後再執行操作的場景。

(四)實作一個簡單的搜尋框 Debounce

解答:以下是一個簡單的搜尋框 Debounce 實作:

// HTML: <input id="search" type="text" placeholder="搜尋...">
// HTML: <div id="results"></div>

function debounce(func, delay) {
  let timer;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => func(...args), delay);
  };
}

const searchInput = document.getElementById('search');
const resultsDiv = document.getElementById('results');

const handleSearch = debounce((event) => {
  const query = event.target.value;
  resultsDiv.textContent = `搜尋中: ${query}`;
  // 實際應用中這裡會發送 API 請求
}, 300);

searchInput.addEventListener('input', handleSearch);