原文:Debounce – How to Delay a Function in JavaScript (JS ES6 Example),作者:Ondrej Polesny
在 JavaScript 中,防抖(debounce)函数可以确保你的代码在每个用户输入时只被触发一次。搜索框建议、文本字段自动保存和消除双键点击都是防抖的用例。
在本教程中,我们将学习如何在 JavaScript 中创建一个防抖函数。
什么是防抖?
防抖这个词来自于电子学。当你按下一个按钮时,比如说在你的电视遥控器上,信号很快就传到了遥控器的微芯片上,在你释放按钮之前,它就反弹了,而微芯片会多次记录你的“点击”。

为了缓解这种情况,一旦收到来自按钮的信号,微芯片就会在几微秒内停止处理来自按钮的信号,而你在物理上不可能再次按下按钮。
JavaScript 中的防抖
在 JavaScript 中,用例是类似的。我们想触发一个函数,但每个用例只触发一次。
比方说,我们想显示一个搜索查询的建议,但只有在访问者完成输入之后。
或者我们想保存表单上的修改,但只在用户没有自己处理这些修改的时候,因为每一次 “保存”都会让数据库运行一次。
我最喜欢的是——有些人真的习惯了 Windows 95,现在双击一切。
这是一个简单的防抖函数的实现(CodePen 示例):
function debounce(func, timeout = 300){
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => { func.apply(this, args); }, timeout);
};
}
function saveInput(){
console.log('Saving data');
}
const processChange = debounce(() => saveInput());
它可以用于 input:
<input type="text" onkeyup="processChange()" />
或者一个按钮:
<button onclick="processChange()">Click me</button>
或者一个窗口事件:
window.addEventListener("scroll", processChange);
而在其他元素上,它的实现就像一个简单的 JS 函数。
那么这里发生了什么?debounce
是一个特殊的函数,它处理两个任务:
- 为定时器 timer 变量分配一个范围
- 安排你的函数在一个特定的时间被触发
让我们解释一下在第一个文本输入的使用案例中是如何工作的。
当访问者写下第一个字母并释放按键时,debounce
首先用 clearTimeout(timer)
重置定时器。在这一点上,这个步骤是没有必要的,因为还没有安排什么。然后,它设置函数 saveInput()
在 300 毫秒内被调用。
但是,假设访问者一直在写,所以每次按键释放都会再次触发 debounce
。每次调用都需要重置定时器,或者换句话说,用 saveInput()
取消之前的计划,并将其重新安排在未来 300 毫秒的新时间。只要访问者一直在 300 毫秒内按下按键,这个过程就会持续下去。
最后的计划不会被清除,所以 saveInput()
最后会被调用。
另一种方式——如何忽略后续事件
这对触发自动保存或显示建议很好。但如果是多次点击一个按钮的用例呢?我们不想等待最后一次点击,而是注册第一次点击,忽略其他的点击(CodePen 示例)。
function debounce_leading(func, timeout = 300){
let timer;
return (...args) => {
if (!timer) {
func.apply(this, args);
}
clearTimeout(timer);
timer = setTimeout(() => {
timer = undefined;
}, timeout);
};
}
这里我们在第一次点击按钮引起的第一次 debounce_leading
调用时触发 saveInput()
函数。我们把定时器的破坏时间安排在 300 毫秒。在这个时间范围内的每一个后续的按钮点击都已经定义了定时器,并且只会将销毁时间推到 300 毫秒以后。
库中的防抖实现
在这篇文章中,我向你展示了如何在 JavaScript 中实现函数防抖,并使用它来防抖网站元素触发的事件。
然而,如果你不想的话,你不需要在你的项目中自己去实现。广泛使用的 JS 库已经包含了防抖实现。这里有几个例子:jQuery (via library)$.debounce(300, saveInput);
Lodash_.debounce(saveInput, 300);
Underscore_.debounce(saveInput, 300);