防抖函数(debounce)和节流函数(throttle),在面试提问,或者是在工作中的日常使用经常出现,但总能遇到将这两个概念弄反的情况。
那让我们通过本文,了解防抖和节流两者的作用和区别。
这两个函数的目的是一致的:防止某个时间段内不断地触发某个事件,或防止不断地发送请求,造成性能消耗。
在未进行优化前,通常是遇到这种情况:
1秒内,因为用户不断地触发事件,发出多个请求。
短时间内发送多个请求,响应的顺序可能会乱序,在前端方面可能会导致展示出错。
用户体量大的项目,单单这一个功能,一个用户就能在一秒内发送七八个请求,如果是几万个用户同时进行这项操作,还会给服务器带来巨大的压力。
那让我们看一下,debounce和throttle是怎么优化这种情景的。
防抖函数(debounce)
在事件触发后的规定时间段内,若事件没有再次触发,则对目标函数进行调用。
在1s内,不断通过操作,发送了多个请求,最常见的就是搜索栏功能,进行的搜索预览展示。
我们需要监听用户输入的内容,在用户输入字符后,我们将关键字通过请求发给后端,再将后端传来的数据进行展示。
此时,防抖函数能很好地优化这种情况,概念如图:
我们可以设定一个时间段(1s)。
操作后的1s内如果进行重复动作,则重新计时。
操作后的1s内没有进行重复动作,则发送请求。
回到上面搜索栏的例子,我们通过一幅图来对比两者的区别:
明显可以对比出,在时间周期内,发送请求的次数能被大幅度优化,减少没必要的请求次数。
防抖函数的时间段,就相当于给用户的操控时间。
防抖函数:”确定是只搜索红这个字吗?给你1秒的时间思考,如果这1秒内你还想输入其他字,那我先不发请求,过了一秒后你还没输入,我就发请求了哈~"
那我们可以看下代码的实现:
functiondebounce(fn,time){
//事件函数
returnfunction(...rest){
//清除上一个启动的计时器
if(fn.timer)clearTimeout(fn.timer)
//存储目标函数传入的实参,通常事件触发会默认携带event属性
letarg=rest;
//存储当前函数的执行上下文this
letcontext=this;
//启动计时器,存储计时器的编号,用于清除
fn.timer=setTimeout(function(){
fn.apply(context,arg);
},time)
}
}
防抖函数的原理,关键在于使用计时器。
在设定的计时范围内,如果用户又重新触发函数,则删除还在计时的计时器,启动一个新的计时器,重新计时。
需要注意的是传参的收集,以及计时器中调用回调函数时的this指向。
节流函数(throttle)
在事件触发后的规定时间段内,无法再调用目标函数。
比较常见的例子,就是验证码功能:
有了防抖函数的例子后,节流函数的概念会更好理解,如下图:
点击发送验证码,在规定时间段内,禁止用户再次点击这个按钮,防止发送多次请求。
那我们可以看下代码的实现:
functionthrottle(fn,time){
//初始化上一次触发事件的时间
varlastTime=0;returnfunction(...rest){
//获取本次事件触发时的时间
varnowTime=Date.now();
//判断本次事件触发时的时间-上一次触发的事件的时间是否小于等待时间
//如果小于等待时间,则return,不继续执行代码
if(nowTime-lastTimetime){return;}
//如果大于等待时间,本次事件触发则继续执行目标函数,更新触发时间
lastTime=nowTime;
//通过扩展运算符,获取传入的所有实参,例如事件触发时,会对绑定的函数传入一个参event
//改变fn的执行上下文this,指向事件触发的对象
fn.apply(this,rest)
}
}
节流函数的原理,关键在于获取函数调用时的时间戳。
对比每次调用的时间戳,是否符合我理想的时间间隔,不符合,则return出去,不继续执行函数,如果符合,则继续执行代码。
同样,需要注意的是传参的收集,以及调用回调函数时的this指向。
总结
防抖函数(debounce)与节流函数(throttle),在提及这两个函数具体的作用和区别之前,先明白它们能解决的相同痛点:都是对”某一时间段内高频率触发目标函数“操作的导致的一系列弊端。
再根据功能的具体情况,决定使用哪种函数:
某一个时间内,需要缓冲时间进行处理操作,再发送请求,则用防抖,像上文提及的搜索栏关键字展示。
某一时间段内,只在第一次触发目标函数,则用节流,像上文提及的验证码。