💡 防抖与节流:前端高手的必备技能,告别卡顿!

70 阅读3分钟

引言

在前端开发中,我们经常会遇到需要频繁触发事件的情况,比如窗口大小调整(resize)、页面滚动(scroll)、输入框实时搜索(input)等。如果每次事件触发都直接执行相应的处理函数,可能会导致性能问题甚至页面卡顿。防抖(debounce)和节流(throttle)就是解决这类问题的两种常用技术。

什么是防抖(Debounce)?

防抖的核心思想是:在事件被触发后,等待一段时间再执行回调函数。如果在这段等待时间内事件又被触发,则重新计时。

应用场景

  • 搜索框输入联想(等待用户停止输入后再发送请求)
  • 窗口大小调整(等待调整结束后再计算布局)
  • 表单验证(用户输入完成后再验证)

实现代码

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

// 使用示例
const searchInput = document.getElementById('search');
searchInput.addEventListener('input', debounce(function() {
  console.log('发送搜索请求:', this.value);
}, 500));

什么是节流(Throttle)?

节流的核心思想是:在一定时间间隔内,只执行一次回调函数,稀释函数的执行频率。

应用场景

  • 页面滚动加载更多(每隔一段时间检查位置)
  • 鼠标移动事件(降低事件触发频率)
  • 游戏中的按键处理(防止按键连发)

实现代码

function throttle(fn, interval) {
  let lastTime = 0;
  return function(...args) {
    const now = Date.now();
    if (now - lastTime >= interval) {
      fn.apply(this, args);
      lastTime = now;
    }
  };
}

// 使用示例
window.addEventListener('scroll', throttle(function() {
  console.log('处理滚动事件');
}, 200));

防抖与节流的区别

特性防抖(Debounce)节流(Throttle)
执行时机事件停止触发后执行固定时间间隔执行
重置机制每次触发都会重置计时器按照固定节奏执行
适用场景关注最终结果的情况关注过程但需要降低频率的情况

进阶实现

带立即执行选项的防抖

function debounce(fn, delay, immediate = false) {
  let timer = null;
  return function(...args) {
    if (timer) clearTimeout(timer);
    
    if (immediate && !timer) {
      fn.apply(this, args);
    }
    
    timer = setTimeout(() => {
      if (!immediate) {
        fn.apply(this, args);
      }
      timer = null;
    }, delay);
  };
}

带取消功能的节流

function throttle(fn, interval) {
  let lastTime = 0;
  let timer = null;
  
  const throttled = function(...args) {
    const now = Date.now();
    const remaining = interval - (now - lastTime);
    
    if (remaining <= 0) {
      if (timer) {
        clearTimeout(timer);
        timer = null;
      }
      fn.apply(this, args);
      lastTime = now;
    } else if (!timer) {
      timer = setTimeout(() => {
        fn.apply(this, args);
        lastTime = Date.now();
        timer = null;
      }, remaining);
    }
  };
  
  throttled.cancel = function() {
    if (timer) {
      clearTimeout(timer);
      timer = null;
    }
  };
  
  return throttled;
}

实际应用中的注意事项

  1. this指向问题:确保回调函数中的this正确指向
  2. 参数传递:正确处理事件回调的参数
  3. 内存泄漏:在组件卸载时取消未执行的防抖/节流函数
  4. 时间间隔选择:根据实际场景选择合适的延迟时间

React/Vue中的使用

在现代前端框架中,我们可以将防抖和节流封装为自定义hook或指令:

React自定义Hook示例

import { useCallback, useRef } from 'react';

function useDebounce(fn, delay) {
  const timerRef = useRef();
  
  return useCallback((...args) => {
    if (timerRef.current) {
      clearTimeout(timerRef.current);
    }
    timerRef.current = setTimeout(() => {
      fn(...args);
    }, delay);
  }, [fn, delay]);
}

// 使用示例
function SearchComponent() {
  const [query, setQuery] = useState('');
  
  const handleSearch = useDebounce((value) => {
    // 执行搜索逻辑
    console.log('搜索:', value);
  }, 500);
  
  return (
    <input 
      value={query}
      onChange={(e) => {
        setQuery(e.target.value);
        handleSearch(e.target.value);
      }}
    />
  );
}

Vue指令示例

// debounce指令
Vue.directive('debounce', {
  inserted(el, binding) {
    let timer;
    el.addEventListener('input', () => {
      clearTimeout(timer);
      timer = setTimeout(() => {
        binding.value();
      }, binding.arg || 500);
    });
  }
});

// 使用示例
<template>
  <input v-debounce:300="search" />
</template>

<script>
export default {
  methods: {
    search() {
      // 搜索逻辑
    }
  }
}
</script>

总结

防抖和节流是前端性能优化的重要手段,理解它们的原理和区别有助于我们在实际开发中选择合适的方案。记住:

  • 当你关心"最终状态"时,使用防抖(如搜索建议)
  • 当你想要"规律性执行"时,使用节流(如滚动事件)
  • 根据实际场景调整时间间隔
  • 注意内存管理和this绑定

合理使用这两种技术可以显著提升页面性能和用户体验。