说在前面的
防抖和节流都是前端日常开发中经常用到的技巧,那么什么是防抖和节流呢?这里线简单介绍一个场景:
假设当前有一个表单页,用户填好表单之后点击按钮提交表单,代码如下:
<button id="submit-btn">Submit</button>
function submit() {
console.log("submit");
}
const btn = document.getElementById("submit-btn");
btn.addEventListener("click", submit);
如果这个时候,如果用户多次点击,效果如下:

很明显提交的请求就会多次被发送到后端,占用资源,这显然是我们不想见到的。
而防抖,节流就是为了应付这样的场景出现的,只是各自效果有一些细微的差别罢了。
防抖
我们首先来看看防抖的效果:

可以看到,当我们点击按钮的时候,并不会立即触发效果,而是在一定的延迟之后才触发效果,并且如果不停的点击按钮,只有在最后一次点击结束之后再经过一定的延迟,才会触发效果。
这就是防抖的功能了:
- 如果被防抖修饰的方法在一定时间内被不断的触发,只有最后一次触发会生效
知道了功能,我们就能根据功能来实现函数了。
首先,我们需要一个防抖函数去修饰提交的方法,所以在点击按钮的时候,我们调用的应该是这个防抖函数:
function submit() {
console.log("submit");
}
const debounce = (fn) =>{
fn()
}
const btn = document.getElementById("submit-btn");
btn.addEventListener("click", debounce(submit));
这个时候,我们点击按钮,效果如下:

可以看到,屏幕都戳烂了也没有触发 submit 的效果,原因是 debounce 虽然调用了 submit 函数,却没有返回,所以这里直接返回一个函数,在函数中调用 fn,顺便再小小优化一下代码:
function submit() {
console.log("submit");
}
const debounce = (fn) =>{
return () => {
fn();
};
}
const oDebounce = debounce(submit);
const btn = document.getElementById("submit-btn");
btn.addEventListener("click", oDebounce);
效果如下:

到这里又回到了最初的状态,不过与一开始的区别是,我们已经可以通过 debounce 来修饰 submit 了,那么再来看看防抖的核心功能
- 如果被防抖修饰的方法在一定时间内被不断的触发,只有最后一次触发会生效
既然要让函数延迟,肯定免不了 setTimeout,那么我们将 fn 放到 setTimeout 中,再加上延迟:
function submit() {
console.log("submit");
}
const debounce = (fn, delay) =>{
return () => {
setTimeout(() => {
fn();
}, delay);
};
}
const oDebounce = debounce(submit, 1000);
const btn = document.getElementById("submit-btn");
btn.addEventListener("click", oDebounce);
效果如下:

可以看到,当连续点击的时候,fn 函数在延迟 1s 之后都一块儿执行了,这就要谈谈 setTimeout 的运行机制了:
setTimeout 的运行机制是,将指定的代码移出本次执行,等到下一 轮Event Loop 时,再检查是否到了指定时间。如果到了,就执行对应的代码;如果不到,就等到再下一轮 Event Loop 时重新判断。
这就很好的解释了,为什么连续触发 setTimeout 的时候,在延迟之后,函数都一块儿触发了。
防抖的需求是,只响应最后一次点击,那么我们可以通过清除定时器来完成这个效果:
function submit() {
console.log("submit");
}
const debounce = (fn, delay) =>{
let timer;
return () => {
clearTimeout(timer);
timer = setTimeout(() => {
fn();
}, delay);
};
}
const oDebounce = debounce(submit, 1000);
const btn = document.getElementById("submit-btn");
btn.addEventListener("click", oDebounce);
效果如下:

到这里已经实现了防抖的基本功能了,当然,我们可以继续优化一下
- 绑定 this 指向
- fn 传参
考虑以上两点,最终代码如下:
function submit() {
console.log("submit");
}
const debounce = (fn, delay) =>{
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
const oDebounce = debounce(submit, 1000);
const btn = document.getElementById("submit-btn");
btn.addEventListener("click", oDebounce);
节流
节流与防抖基本是类似的,同样的,我们先来看看节流的效果:

可以看出,节流的效果是:
- 在一定时间之内,被节流函数修饰的函数只会触发一次
结合这个功能,很容易就能想到下面的思路:
- setp1:我们设置一个开关,当节流函数启动的时候关掉这个开关
- step2:当开关关闭的时候对外界不做任何响应
- step3:当节流函数执行完毕之后重新打开开关
有了这个思路,节流函数的实现也就差不多了(这里顺带把优化也做了):
const throttle = (fn, delay) => {
let tSwitch = true;
return (...args) => {
if (!tSwitch) return;
tSwitch = false;
setTimeout(() => {
fn.apply(this, args);
tSwitch = true;
}, delay);
};
};
结束语
这里是 在线 demo