Skip to content

JavaScript 防抖与节流

前言

在前端开发中,防抖(Debounce)和节流(Throttle)是两种重要的性能优化技术。它们主要用于处理高频触发的事件,如滚动、搜索、窗口调整等。本文将详细介绍这两种技术的原理、实现及应用场景。

一、基本概念

1.1 什么是防抖和节流?

  • 防抖(Debounce): 在事件被触发n秒后再执行回调,如果在这n秒内事件又被触发,则重新计时。
  • 节流(Throttle): 规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行。

1.2 形象比喻

想象一个电梯的运行策略:

  • 防抖: 电梯等待15秒后才运行,期间有人进来则重新计时15秒
  • 节流: 电梯第一个人进来后,15秒后准时运行,不等待

二、防抖(Debounce)实现

2.1 基础版本

javascript
function debounce(func, wait) {
    let timer = null;
    
    return function (...args) {
        // 清除之前的定时器
        if (timer) clearTimeout(timer);
        
        // 设置新的定时器
        timer = setTimeout(() => {
            func.apply(this, args);
        }, wait);
    }
}

// 使用示例
const handleSearch = debounce(function(e) {
    console.log('搜索内容:', e.target.value);
}, 500);

searchInput.addEventListener('input', handleSearch);

2.2 带立即执行的完整版本

javascript
function debounce(func, wait, immediate = false) {
    let timer = null;
    let result;
    
    const debounced = function (...args) {
        // 清除之前的定时器
        if (timer) clearTimeout(timer);
        
        if (immediate) {
            // 如果是立即执行
            const callNow = !timer;
            
            timer = setTimeout(() => {
                timer = null;
            }, wait);
            
            if (callNow) {
                result = func.apply(this, args);
            }
        } else {
            // 延迟执行
            timer = setTimeout(() => {
                func.apply(this, args);
            }, wait);
        }
        
        return result;
    };
    
    // 提供取消功能
    debounced.cancel = function() {
        clearTimeout(timer);
        timer = null;
    };
    
    return debounced;
}

三、节流(Throttle)实现

3.1 时间戳版本

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

3.2 定时器版本

javascript
function throttle(func, wait) {
    let timer = null;
    
    return function (...args) {
        if (!timer) {
            timer = setTimeout(() => {
                func.apply(this, args);
                timer = null;
            }, wait);
        }
    }
}

3.3 结合版本(更精确的节流)

javascript
function throttle(func, wait) {
    let timer = null;
    let previous = 0;
    
    return function (...args) {
        const now = Date.now();
        const remaining = wait - (now - previous);
        
        if (remaining <= 0) {
            // 时间到了,可以执行
            if (timer) {
                clearTimeout(timer);
                timer = null;
            }
            
            func.apply(this, args);
            previous = now;
        } else if (!timer) {
            // 设置定时器,保证最后一次也能执行
            timer = setTimeout(() => {
                func.apply(this, args);
                previous = Date.now();
                timer = null;
            }, remaining);
        }
    }
}

四、应用场景

4.1 防抖应用场景

  1. 搜索框输入
javascript
const searchInput = document.getElementById('search');
const handleSearch = debounce(async (e) => {
    const value = e.target.value;
    const results = await fetchSearchResults(value);
    updateUI(results);
}, 500);

searchInput.addEventListener('input', handleSearch);
  1. 窗口调整
javascript
const handleResize = debounce(() => {
    console.log('窗口大小改变了!');
    // 重新计算布局
}, 250);

window.addEventListener('resize', handleResize);
  1. 表单验证
javascript
const validateEmail = debounce(async (email) => {
    const isValid = await checkEmailExists(email);
    updateValidationUI(isValid);
}, 400);

4.2 节流应用场景

  1. 滚动加载
javascript
const handleScroll = throttle(() => {
    const { scrollTop, scrollHeight, clientHeight } = document.documentElement;
    
    if (scrollTop + clientHeight >= scrollHeight - 20) {
        loadMoreData();
    }
}, 200);

window.addEventListener('scroll', handleScroll);
  1. 按钮点击
javascript
const button = document.getElementById('submit');
const handleSubmit = throttle(async () => {
    await submitForm();
}, 1000);

button.addEventListener('click', handleSubmit);

五、注意事项

5.1 防抖注意点

  1. 是否需要立即执行
  2. 是否需要取消功能
  3. 返回值的处理
  4. this指向问题

5.2 节流注意点

  1. 首次是否执行
  2. 最后一次是否执行
  3. 时间戳还是定时器方式
  4. 是否需要取消功能

六、最佳实践

  1. 选择合适的时间间隔
javascript
// 搜索框防抖:300-500ms
const searchDebounce = debounce(search, 300);

// 滚动节流:100-200ms
const scrollThrottle = throttle(onScroll, 150);
  1. 及时清理
javascript
// React组件示例
useEffect(() => {
    const handleScroll = throttle(onScroll, 200);
    window.addEventListener('scroll', handleScroll);
    
    return () => {
        window.removeEventListener('scroll', handleScroll);
        handleScroll.cancel?.();
    };
}, []);

总结

防抖和节流都是优化高频率执行代码的手段:

  1. 防抖: 适合连续事件只需要触发一次的场景
  2. 节流: 适合需要定期执行的场景

关键区别:

  • 防抖是在最后一次事件后才执行
  • 节流是在一段时间内只执行一次

选择使用哪种方式,主要取决于具体的业务场景和需求。