面试必问,防抖函数的核心是什么?
防抖节流的作用是什么?
节流(throttle)与 防抖(debounce)都是为了限制函数的执行频次,以优化函数触发频率过高导致的响应速度跟不上触发频率,出现延迟,假死或卡顿的现象。
防抖:是指在一定时间内,在动作被连续频繁触发的情况下,动作只会被执行一次,也就是说当调用动作过n毫秒后,才会执行该动作,若在这n毫秒内又调用此动作则将重新计算执行时间,所以短时间内的连续动作永远只会触发一次,比如说用手指一直按住一个弹簧,它将不会弹起直到你松手为止。
节流:是指一定时间内执行的操作只执行一次,也就是说即预先设定一个执行周期,当调用动作的时刻大于等于执行周期则执行该动作,然后进入下一个新周期,一个比较形象的例子是人的眨眼睛,就是一定时间内眨一次。
防抖函数应用场景:
就比如说这段代码:
let btn = document.getElementById('btn')
btn.addEventListener('click', function() {
console.log('提交'); // 换成ajax请求
})
当你点击按钮N下,它就会打印N次“提交”,但如果把 console 换成 ajax 请求,可想而知后端接受到触发频率如此之高的请求,造成的页面卡顿甚至瘫痪的后果。
防抖函数的核心:
面对此种情形,我们必须在原有的基础上作出改进,做到在规定的时间内没有下一次的触发,才执行的效果。
那么首先我们要做的,就是创建一个防抖函数,这个函数的功能是设置一个定时器,每次点击都会触发一个定时器输出,但如果两次点击的间隔小于1s,则销毁上一个定时器,达到最后只有一个定时器输出的效果。
定时器:
在防抖节流中,最为重要的一个部分就是定时器,就比如下面这段代码,
setTimeout
的功能就是设置一个定时器,让setTimeout
内部的代码延迟执行在 1000 毫秒后。
setTimeout(function(){
console.log('提交');
}, 1000)特别需要注意一点的是,定时器中回调函数里的 this 指向会更改成指向 window。
于是我们创建专门的debounce
函数用于实现防抖,把handle
交给debounce
处理,再在debounce
内部设置一个setTimeout
定时器,将handle
的执行推迟到点击事件发生的一秒后,这样一来,我们就实现了初步的想法。
let btn = document.getElementById('btn')
function handle(){
console.log('提交', this); // 换成ajax请求
}
// 创建专门的debounce函数用于防抖,把handle交给debounce处理
btn.addEventListener('click', debounce(handle))
// 将点击事件推迟一秒
function debounce(fn){
return function() {
// 设置定时器
setTimeout(fn, 1000)
}
}
那么关键来了,我们又在原基础上添加一个timer
用于接收定时器返回的值(通常称为定时器的ID),然后设置clearTimeout(timer)
通过timer
取消之前通过 setTimeout
创建的定时器。
通过这段代码,我们便实现了如果在 1s 内频繁点击的话,上一次点击的事件都会被下一次点击取消,从而达到规定的时间内没有下一次的触发,再执行的防抖目的!
let btn = document.getElementById('btn')
function handle(){
console.log('提交', this); // 换成ajax请求
}
// 创建专门的debounce函数用于防抖,把handle交给debounce处理
btn.addEventListener('click', debounce(handle))
// 防抖函数
function debounce(fn){
let timer = null; // 接收定时器返回的ID
return function() {
// 设置定时器
clearTimeout(timer); // 取消之前通过 `setTimeout` 创建的定时器
timer = setTimeout(fn, 1000);
}
}
但是别忘了,我们之前提到过,定时器改变了handle
中 this 指向,要做到尽善尽美,我们必须通过显示绑定修正 this 的指向。
同时别忘记还原原函数的参数。
利用箭头函数不承认 this 的特性,我们将代码修改成这样:
let btn = document.getElementById('btn')
function handle(e){
console.log('提交'); // 换成ajax请求
}
// 创建专门的debounce函数用于防抖,把handle交给debounce处理
btn.addEventListener('click', debounce(handle))
// 防抖函数
function debounce(fn){
let timer = null; // 接收定时器返回的ID
return function(e) {
// 设置定时器
clearTimeout(timer);
timer = setTimeout(() => {
fn.call(this,e); // 修正this的同时归还原函数的参数
}, 1000)
}
}
至此,大功告成!
防抖函数核心机制:
同时需要理解的是:防抖函数的核心机制就是闭包,当每一次点击会产生debounce
执行上下文,随后debounce
执行完其上下文又被反复销毁,但是其中的变量timer
又始终保持着对function
外部的引用,于是由此形成了闭包。
关于 this 的指向可以参考这篇文章:juejin.cn/post/739763…
关于闭包概念可以参考这篇文章:juejin.cn/post/739762…
最后:
那么现在我们可以总结出这个防抖函数的核心理念和四大要点。
核心理念:点击按钮后,做到在规定的时间内没有下一次的触发,才执行
- 其中
debounce
返回一个函数体,跟debounce
形成了一个闭包。 - 子函数体中每次先销毁上一个
setTimeout
,再创建一个新的setTimeout
。 - 最后需要 还原原函数的 this 指向。
- 最后需要 还原原函数的参数。
来源:juejin.cn/post/7400253623790272547