前端接口节流(Throttle)与防抖(Debounce)详解
我做了一个修改账单功能,输入框更新之后立即保存用户输入,可以通过input标签的change事件来触发保存逻辑,可是用户输入了100个文字,那保存接口岂不是要调用100次???有没有什么办法可以确保用户输入的信息一定会保存下来,又控制住保存逻辑不要调用的那么频繁呢?有的兄弟,有的,这就是接下来要讲的 节流和防抖。
一 核心区别
特性 | 节流(Throttle) | 防抖(Debounce) |
---|---|---|
执行时机 | 固定时间间隔执行一次 | 停止触发后延迟执行 |
响应速度 | 即时响应但频率受限 | 延迟响应 |
适用场景 | 需要规律性执行的情况(如滚动事件) | 只需最终结果的情况(如搜索输入) |
类比 | 像机枪的射速限制(每秒固定次数) | 像电梯门(等人进入后延迟关闭) |
二 定时器实现代码
1. 节流(Throttle)实现
function throttle(fn, delay) {
let timer = null;
return function(...args) {
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, args);
timer = null;
}, delay);
}
};
}
执行逻辑:
- 首次调用时,
timer
为null
,立即设置定时器 - 在
delay
时间内再次调用,因timer
存在,不会执行新操作 - 定时器到期后执行函数并清空
timer
,允许下一次调用
2. 防抖(Debounce)实现
function debounce(fn, delay) {
let timer = null;
return function(...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
执行逻辑:
- 每次调用都清除之前的定时器
- 设置新的定时器,延迟
delay
执行 - 只有最后一次调用后的
delay
时间内没有新调用,才会真正执行
三 代码执行对比示例
假设连续快速触发事件,delay
设为500ms:
时间轴(ms): 0---100---200---300---400---500---600---700---800
触发事件: * * * * * * * * *
节流输出: |_________|_________|_________| (每500ms固定一次)
防抖输出: |___________________| (最后一次触发后500ms)
四 实际应用场景
适合节流的情况:
- 滚动事件处理
- 鼠标移动跟踪
- 游戏中的按键处理
- 窗口大小调整时的计算
适合防抖的情况:
- 搜索框输入建议
- 表单验证
- 自动保存功能
- 防止按钮重复提交
五 高级组合使用
有时需要结合两种技术:
// 首次立即执行,停止触发后再执行一次
function enhancedThrottle(fn, delay) {
let timer = null;
let lastExec = 0;
return function(...args) {
const now = Date.now();
const remaining = delay - (now - lastExec);
if (remaining <= 0) {
fn.apply(this, args);
lastExec = now;
} else {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
lastExec = Date.now();
}, remaining);
}
};
}
理解节流和防抖的区别及实现原理,能帮助你在实际开发中选择最合适的优化方案。
六 Lodash 中的节流与防抖功能
Lodash 提供了非常完善的节流(throttle)和防抖(debounce)功能,并且支持多种高级配置选项。
1. 基本接口
防抖 (debounce)
_.debounce(func, [wait=0], [options={}])
参数:
func
: 要防抖的函数wait
: 延迟的毫秒数options
: 配置对象leading
: 指定在延迟开始前调用(默认false)trailing
: 指定在延迟结束后调用(默认true)maxWait
: 设置最大等待时间
节流 (throttle)
_.throttle(func, [wait=0], [options={}])
参数:
func
: 要节流的函数wait
: 节流的毫秒数options
: 配置对象leading
: 指定在节流开始前调用(默认true)trailing
: 指定在节流结束后调用(默认true)
2. 同时使用节流和防抖
Lodash 的 debounce
函数通过 maxWait
选项可以实现类似节流+防抖的效果:
// 结合防抖和节流的效果
// 确保至少每500ms执行一次,但最后一次调用后也会执行
const combined = _.debounce(func, 250, {
maxWait: 500,
leading: true,
trailing: true
});
这种配置实现了:
- 防抖特性:快速连续调用时,只在停止调用250ms后执行
- 节流特性:无论如何,至少每500ms会执行一次
- leading:首次调用立即执行
- trailing:最后一次调用也会执行
3. 实际使用示例
纯防抖示例(搜索建议)
const search = _.debounce((query) => {
console.log(`Searching for: ${query}`);
// 实际搜索API调用
}, 300);
// 用户输入时
search('a');
search('ab');
search('abc'); // 只有这个会执行
纯节流示例(滚动事件)
const handleScroll = _.throttle(() => {
console.log('Handling scroll');
// 滚动处理逻辑
}, 100);
window.addEventListener('scroll', handleScroll);
结合节流和防抖的示例(实时保存)
const saveContent = _.debounce(
(content) => {
console.log('Saving:', content);
// 保存API调用
},
1000, // 防抖延迟1秒
{
maxWait: 5000, // 但最多每5秒必须保存一次
leading: false,
trailing: true
}
);
// 用户编辑时
contentEditor.on('change', saveContent);
4. 取消和刷新方法
Lodash 的节流和防抖函数返回的函数还提供了额外方法:
const throttled = _.throttle(/* ... */);
// 取消延迟的函数调用
throttled.cancel();
// 立即调用被延迟的函数
throttled.flush();
// 检查是否有等待执行的函数调用
throttled.pending();
5. 为什么选择 Lodash 的实现
- 更精确的定时控制:比原生实现更精确的时间管理
- 丰富的配置选项:可以灵活控制首尾调用行为
- 附加功能:提供取消、刷新等方法
- 性能优化:经过充分测试和优化
- 浏览器兼容性:处理了各浏览器的边缘情况
Lodash 的这些函数非常适合处理复杂的节流和防抖需求,特别是在需要同时考虑即时响应和性能优化的场景下。