防抖
什么是防抖
防抖:无论用户触发多少次事件,对应的回调函数只会在事件停止触发指定事件后执行。(即:回调函数在事件停止触发指定时间后被调用)
通俗地说,就是当我们触发一个事件后,不管在这段时间内这个事件又被触发多少次,这个事件的后果都只会产生一次。
场景举例:当用户在提交表单的时候,有可能会因为网络缓慢的原因,表单提交成功的通知迟迟未显示出来,因此可能会重复点击表单的提交按钮,此时如果没有为表单提交事件实现防抖效果,表单的信息就会被提交多次,但我们只需要一个表单就够了,而如果实现了防抖效果,不管用户在这段时间内点击提交了多少次,最后只会提交一个表单信息,也就是最后一次提交的表单信息。
如何实现防抖
主要通过使用计时器setTimeout来实现,给我们触发的时间添加一个计时器,一段时间后才会执行,在这段时间内如果事件又被触发,就会刷新计时器,最后计时结束后执行回调函数。
代码实现
为表单按钮添加事件监听,当被点击时就会调用debounce 函数返回的函数,debounce函数接收两个参数,分别是我们希望实现防抖的函数(即事件触发最后产生后果)和防抖的持续时间。
debounce 函数的作用是包装最终希望执行的函数,为其实现防抖效果。首先需要在函数体内声明一个timer 变量用于存储计时器,当事件触发时,在返回的函数中创建计时器,如果事件在规定的防抖时间内再次触发,就会通过clearTimeout 的方式清除timer 变量中存储的计时器并重新创建一个计时器。
在这里我们引出两个问题:
- 为什么需要在返回函数外部定义timer?
因为每次调用 debounce 返回的函数时,都需要访问和修改同一个timer,以便在新的事件触发时清除之前的定时器。
如果 timer 变量定义在返回的函数内部,那么每次调用时都会重新创建一个新的 timer 变量,无法清除上一个定时器,也就无法实现防抖的效果。
- 返回的函数可以访问到这个timer吗?
timer 变量是在 debounce 函数的作用域内声明的,这意味着它只在 debounce 函数体内可见。但是,由于 debounce 返回了一个内部函数,由于闭包的特效,这个内部函数会“记住”它创建时的环境,因此它可以访问 timer。
即使 debounce 函数已经执行完毕并且其上下文已经被销毁。由于闭包的特性,timer 依然保留在内存中,因为返回的函数对它有引用。
至此,我们已经可以正确的创建和销毁计时器,基本实现了防抖的效果,但是还有一个问题需要解决,当一个函数作为 setTimeout 的回调函数被调用时,this 默认会指向全局对象,但我们希望它this与debounce 返回的函数的上下文一致,arguments也一样。
因此我们通过闭包保存正确的this和arguments,并使用 apply 方法在 setTimeout 回调中确保 this 指向正确的对象,确保调用func 时的上下文与调用 debounce 返回函数时的一致。
const button = document.getElementById("debounceButton");
const submit = () => {
console.log("Form submitted");
};
const debounce = (func, delay) => {
let timer;
return function () {
let context = this;
let args = arguments;
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(context, args);
}, delay);
};
};
button.addEventListener("click", debounce(submit, 1000));