一、为什么需要节流与防抖?
在前端开发中,高频触发的事件(如滚动、输入、窗口调整)会导致性能问题。例如:
- 用户快速输入时,
input事件频繁触发验证逻辑,造成卡顿; - 滚动页面时,
scroll事件触发大量计算,导致页面抖动; - 按钮被快速点击时,重复提交请求,浪费服务器资源。
核心矛盾:事件触发频率与代码执行效率的冲突。节流(Throttle)和防抖(Debounce)正是解决这一问题的两种经典策略。
二、节流(Throttle):控制执行频率
1. 原理与实现
节流的核心思想是:固定时间间隔内只执行一次函数。例如,每200ms执行一次,无论事件触发多少次。
function throttle(func, delay) {let lastTime = 0;return function(...args) {const now = Date.now();if (now - lastTime >= delay) {func.apply(this, args);lastTime = now;}};}
关键点:
- 记录上一次执行时间(
lastTime); - 仅当当前时间与上次执行时间的间隔≥
delay时,才执行函数。
2. 使用场景
- 滚动事件(Scroll):避免频繁计算滚动位置或触发动画。
window.addEventListener('scroll', throttle(() => {console.log('Scroll position:', window.scrollY);}, 200));
- 窗口调整(Resize):防止频繁重排/重绘。
window.addEventListener('resize', throttle(() => {console.log('Window size:', window.innerWidth);}, 300));
- 高频点击按钮:限制请求发送频率。
const submitBtn = document.getElementById('submit');submitBtn.addEventListener('click', throttle(() => {fetch('/api/submit');}, 1000));
3. 节流的变体:尾调用优化
默认节流可能在时间间隔结束时漏掉最后一次触发。若需确保最后一次触发被执行,可结合setTimeout:
function throttle(func, delay) {let lastTime = 0;let timeoutId;return function(...args) {const now = Date.now();const remaining = delay - (now - lastTime);if (remaining <= 0) {clearTimeout(timeoutId);func.apply(this, args);lastTime = now;} else if (!timeoutId) {timeoutId = setTimeout(() => {func.apply(this, args);lastTime = Date.now();timeoutId = null;}, remaining);}};}
三、防抖(Debounce):延迟执行
1. 原理与实现
防抖的核心思想是:事件触发后,等待一段时间再执行函数,若期间再次触发则重新计时。例如,用户停止输入500ms后执行搜索。
function debounce(func, delay) {let timeoutId;return function(...args) {clearTimeout(timeoutId);timeoutId = setTimeout(() => {func.apply(this, args);}, delay);};}
关键点:
- 每次触发事件时,清除之前的定时器;
- 重新设置定时器,仅当定时器结束时执行函数。
2. 使用场景
- 搜索框输入(Input):避免每次输入都发送请求。
const searchInput = document.getElementById('search');searchInput.addEventListener('input', debounce((e) => {fetch(`/api/search?q=${e.target.value}`);}, 500));
- 表单验证:用户停止输入后统一验证。
const formInput = document.getElementById('username');formInput.addEventListener('input', debounce(() => {validateUsername();}, 300));
- 自动保存:用户停止编辑后保存数据。
const editor = document.getElementById('editor');editor.addEventListener('input', debounce(() => {saveContent();}, 1000));
3. 防抖的变体:立即执行版
若需在第一次触发时立即执行,后续触发再防抖,可添加immediate参数:
function debounce(func, delay, immediate = false) {let timeoutId;return function(...args) {if (immediate && !timeoutId) {func.apply(this, args);}clearTimeout(timeoutId);timeoutId = setTimeout(() => {if (!immediate) {func.apply(this, args);}timeoutId = null;}, delay);};}
四、节流与防抖的对比
| 特性 | 节流(Throttle) | 防抖(Debounce) |
|---|---|---|
| 执行时机 | 固定间隔执行 | 停止触发后延迟执行 |
| 适用场景 | 需要持续反馈(如滚动、动画) | 需要最终结果(如输入、搜索) |
| 资源消耗 | 稳定,但可能漏掉最后一次触发 | 低,但可能延迟执行 |
五、实战建议
- 优先使用 Lodash:Lodash 的
_.throttle和_.debounce已处理边界情况(如this绑定、取消功能)。import { throttle, debounce } from 'lodash';window.addEventListener('scroll', throttle(() => { /* ... */ }, 200));
- 取消功能:若需在组件卸载时取消定时器,可返回取消函数:
function debounce(func, delay) {let timeoutId;const debounced = function(...args) { /* ... */ };debounced.cancel = () => clearTimeout(timeoutId);return debounced;}
- 性能监控:结合
performance.now()测量执行时间,优化延迟参数。
六、总结
- 节流:控制执行频率,适合持续触发的事件(如滚动、点击)。
- 防抖:延迟执行,适合需要最终结果的事件(如输入、调整窗口)。
- 选择策略:根据业务需求(实时性 vs 性能)决定使用节流或防抖。
通过合理应用节流与防抖,可显著提升页面性能,避免不必要的计算和请求,为用户提供流畅的交互体验。