魯斯前端布魯斯前端

文章中英模式

布魯斯前端JS面試題目 - 實作防抖函數處理輸入事件

學習如何實作防抖函數來優化表單輸入、搜尋建議和按鈕點擊等事件,提升使用者體驗與前端效能。

影片縮圖

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

什麼是防抖 (Debounce)?

防抖是一種前端最佳化技術,用於控制函數觸發的頻率,尤其適用於處理使用者快速連續操作,如:輸入框打字、視窗調整大小、滾動事件等。

核心原理:延遲執行函數,若指定時間內觸發多次,則以最後一次操作為準。

防抖應用場景

  • 1. 搜尋框輸入建議(等用戶停止輸入後才發送請求)
  • 2. 表單驗證(用戶輸入完畢後再驗證)
  • 3. 視窗調整大小時的重新計算
  • 4. 避免按鈕重複點擊提交
  • 5. 即時儲存文章草稿

基本實作:防抖函數 (Debounce)

下面是一個簡單的防抖函數實現,接收一個函數和延遲時間作為參數:

function debounce(func, delay = 300) {
  let timer = null;
  
  return function(...args) {
    // 保存 this 上下文
    const context = this;
    
    // 清除之前的計時器
    clearTimeout(timer);
    
    // 設置新的計時器
    timer = setTimeout(() => {
      func.apply(context, args);
    }, delay);
  };
}

使用範例:搜尋輸入框

// 定義一個搜尋函數
function search(query) {
  console.log("搜尋:", query);
  // 通常這裡會有 API 請求
}

// 創建防抖版本的搜尋函數 (500ms延遲)
const debouncedSearch = debounce(search, 500);

// 綁定到輸入事件
document.querySelector('#search-input').addEventListener('input', function(e) {
  debouncedSearch(e.target.value);
});

進階實作:帶立即執行選項的防抖

有時我們需要首次觸發立即執行,之後再進行防抖,例如:表單驗證時希望立即反饋首次輸入。

function advancedDebounce(func, delay = 300, immediate = false) {
  let timer = null;
  
  return function(...args) {
    const context = this;
    const callNow = immediate && !timer;
    
    clearTimeout(timer);
    
    timer = setTimeout(() => {
      timer = null;
      
      if (!immediate) {
        func.apply(context, args);
      }
    }, delay);
    
    // 如果是立即執行模式且首次觸發,立即調用函數
    if (callNow) {
      func.apply(context, args);
    }
  };
}

使用範例:表單驗證

// 驗證電子郵件格式
function validateEmail(email) {
  const emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,6}$/;
  const isValid = emailRegex.test(email);
  
  if (!isValid) {
    document.querySelector('#email-error').textContent = '請輸入有效的電子郵件';
  } else {
    document.querySelector('#email-error').textContent = '';
  }
}

// 立即驗證首次輸入,之後等用戶輸入完畢再驗證
const debouncedValidate = advancedDebounce(validateEmail, 500, true);

document.querySelector('#email-input').addEventListener('input', function(e) {
  debouncedValidate(e.target.value);
});

防抖函數處理常見問題

1. 如何處理 this 綁定問題

在類組件中使用防抖時,需要正確處理 this 的綁定,可以使用箭頭函數或在構造函數中綁定。

class SearchComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { query: '' };
    
    // 綁定 this 到防抖後的方法
    this.debouncedSearch = debounce(this.search.bind(this), 500);
    // 或直接使用箭頭函數
    // this.search = () => { /* ... */ };
    // this.debouncedSearch = debounce(this.search, 500);
  }
  
  search() {
    // 這裡可以安全地使用 this
    console.log('搜尋:', this.state.query);
  }
  
  handleChange = (e) => {
    this.setState({ query: e.target.value });
    this.debouncedSearch();
  }
  
  render() {
    return (
      <input 
        type="text" 
        value={this.state.query} 
        onChange={this.handleChange} 
      />
    );
  }
}

2. 取消防抖執行

有時我們需要取消延遲執行,例如組件卸載時取消尚未執行的調用:

function debounceWithCancel(func, delay = 300) {
  let timer = null;
  
  function debounced(...args) {
    const context = this;
    clearTimeout(timer);
    
    timer = setTimeout(() => {
      func.apply(context, args);
    }, delay);
  }
  
  // 添加取消方法
  debounced.cancel = function() {
    clearTimeout(timer);
    timer = null;
  };
  
  return debounced;
}

// 在 React 組件中使用
useEffect(() => {
  const debouncedFunc = debounceWithCancel(myFunction, 500);
  
  // 注冊事件監聽
  element.addEventListener('input', debouncedFunc);
  
  // 清理
  return () => {
    element.removeEventListener('input', debouncedFunc);
    debouncedFunc.cancel(); // 取消尚未執行的調用
  };
}, []);