BRUCE_FEBRUCE_FE

EN/CH Mode

BRUCE_FE JS Interview Notes - Implement Debounce Function for Input Events

Learn how to implement debounce functions to optimize form input, search suggestions, and button click events, improving user experience and frontend performance.

ๅฝฑ็‰‡็ธฎๅœ–

Lazy to read articles? Then watch videos!

What is Debounce?

Debounce is a frontend optimization technique used to control the frequency of function triggers, especially suitable for handling rapid consecutive user operations such as typing in input fields, window resizing, scroll events, etc.

Core principle: Delay function execution, and if triggered multiple times within a specified time, use the last operation as the standard.

Debounce Application Scenarios

  • 1. Search input suggestions (send requests only after user stops typing)
  • 2. Form validation (validate after user finishes input)
  • 3. Recalculation when window is resized
  • 4. Prevent duplicate button click submissions
  • 5. Real-time article draft saving

Basic Implementation: Debounce Function

Below is a simple debounce function implementation that takes a function and delay time as parameters:

function debounce(func, delay = 300) {
  let timer = null;
  
  return function(...args) {
    // Save this context
    const context = this;
    
    // Clear previous timer
    clearTimeout(timer);
    
    // Set new timer
    timer = setTimeout(() => {
      func.apply(context, args);
    }, delay);
  };
}

Usage Example: Search Input Box

// Define a search function
function search(query) {
  console.log("Search:", query);
  // Usually there would be an API request here
}

// Create debounced version of search function (500ms delay)
const debouncedSearch = debounce(search, 500);

// Bind to input event
document.querySelector('#search-input').addEventListener('input', function(e) {
  debouncedSearch(e.target.value);
});

Advanced Implementation: Debounce with Immediate Execution Option

Sometimes we need the first trigger to execute immediately, then apply debounce afterward, for example: when validating forms, we want immediate feedback for the first input.

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 immediate execution mode and first trigger, call function immediately
    if (callNow) {
      func.apply(context, args);
    }
  };
}

Usage Example: Form Validation

// Validate email format
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 = 'Please enter a valid email';
  } else {
    document.querySelector('#email-error').textContent = '';
  }
}

// Validate first input immediately, then wait for user to finish typing
const debouncedValidate = advancedDebounce(validateEmail, 500, true);

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

Common Issues with Debounce Function Implementation

1. How to Handle this Binding Issues

When using debounce in class components, you need to properly handle this binding, you can use arrow functions or bind in the constructor.

class SearchComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { query: '' };
    
    // Bind this to debounced method
    this.debouncedSearch = debounce(this.search.bind(this), 500);
    // Or use arrow function directly
    // this.search = () => { /* ... */ };
    // this.debouncedSearch = debounce(this.search, 500);
  }
  
  search() {
    // Can safely use this here
    console.log('Search:', 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. Cancel Debounce Execution

Sometimes we need to cancel delayed execution, for example, cancel pending calls when components unmount:

function debounceWithCancel(func, delay = 300) {
  let timer = null;
  
  function debounced(...args) {
    const context = this;
    clearTimeout(timer);
    
    timer = setTimeout(() => {
      func.apply(context, args);
    }, delay);
  }
  
  // Add cancel method
  debounced.cancel = function() {
    clearTimeout(timer);
    timer = null;
  };
  
  return debounced;
}

// Usage in React component
useEffect(() => {
  const debouncedFunc = debounceWithCancel(myFunction, 500);
  
  // Register event listener
  element.addEventListener('input', debouncedFunc);
  
  // Cleanup
  return () => {
    element.removeEventListener('input', debouncedFunc);
    debouncedFunc.cancel(); // Cancel pending calls
  };
}, []);