防抖(debounce)/节流函数(throttle)
由于发现其实很难找到比较完整的防抖和节流函数,我想着自己摸索出来了,就进行一个分享,希望能够帮助到大家,也请多多指导。
首先说防抖debounce(func,wait)
要封装一个防抖/节流函数,它们会接受到两个参数
- func:要添加防抖机制的函数
- wait:规定的等待时间
概念解析 :多次点击只执行最后一次,防止连点
工具 :setTimeout(callback,duration)
计时器
依靠原理 :JS闭包
闭包的特性 :一个函数声明了一系列变量 ,且返回一个函数(暂且叫它packFunc),多次调用packFunc,它们共享这些变量 ,这就是闭包的特性。
实现逻辑 :维护一个计时器对象timeout,wait时间内,多次点击则刷新计时器等待wait秒,wait秒一过,则执行func。
写一个防抖函数:
js
function debounce(func,wait){
let timeout;
return function(...args){
const context = this; // 维护本层作用域中的this
clearTimeout(timeout); // 如果timeout为空并不影响
timeout = setTimeout(()=>{
func.apply(context,args) // 用到这里来,让func中的this指向调用packFunc的对象
},wait);
}
}
描述:防抖函数维护一个timeout闭包变量,返回一个函数packFn
(因为这里是要打包成防抖的函数,我这里暂称pakcFn),packFn独享这个timeout闭包变量,多次调用packFn
,可访问到的packFn相同。
【packFn 】描述:
【接收参数】:接收若干个参数,所有参数放到args数组中
【函数逻辑】:刷新计时器,计时器内部执行func.apply(context,args);
然后说节流
节流有两种,分别是立即执行方案(leading)和延迟执行方案(trailing)
- 立即执行采用:时间戳方案
- 延时执行采用:计时器方案
要封装一个防抖/节流函数,它们会接受到两个参数
- func:要添加防抖机制的函数
- limit:节流时长
节流的依靠原理仍然是JS的闭包,即函数中的变量可以被其返回的函数一直访问,且是独立专属于它的闭包变量。
时间戳方案(leading即时执行):
实现逻辑:维护一个闭包变量lastTime,当lastTime = 0 或者 当前时间now - lastTime > wait时,说明是首次执行,或上一次节流已经结束,则执行func.apply(context,args),否则什么都不做。 代码如下:
js
function throttle(func,wait){
let lastTime = 0;
let context = this;
return function(...args){
let now = Date.now();
if(lastTime === 0 || now - lastTime > delay){
func.apply(context,args);
lastTime = now;
}
}
}
【packFn 】描述:
【接收参数】:...args表示接收若干个参数,都放入args数组中,之后可以解构拿取。
【函数逻辑】:首先获取触发时间,进行判断:如果lastTime为0说明首次点击,可执行func,并更新lastTime = now,或者距离上次点击已经超过节流冷却时间也可执行func,并更新lastTime = now,否则什么都不做。
计时器方案(trailing延时执行):
延时执行:在节流期间的最后才执行。 【实现逻辑】:维护一个闭包变量timeout,当timeout为空则说明是首次或者上一次节流已结束timeout重置,即目前未进行节流,则开启节流,否则直接return。 代码如下:
js
function throttle(func,wait){
let timeout;
return function(...args){
let context = this;
if(tiemout) return;
timeout = setTimeout(()=>{
func.apply(context,args);
timeout = null;
},wait);
}
}
【packFn 】描述:
【接收参数】:若干,放在args数组中。
【函数逻辑】:如果timeout存在则说明处于节流期间,不作处理直接return,timeout没创建说明还没进行节流,则setTimeout,逻辑为当节流时间到,则执行func.apply(context,args)并将timeout重置。
精粹总结
函数命名规范:debounce()、throttle()
接收参数:func,wait,分别为要添加机制的函数 和 拟定等待时间
返回函数packFun:接收参数...args,即所有参数放在args数组中,函数体内部可以解构获取。
封装时,创建要维护的变量,执行后形成一个闭包,返回一个函数packFun,packFun可以访问专属于这个函数packFun的闭包所维护的变量
防抖:维护一闭包个变量timeout,一定时间内多次执行函数,则刷新timout,计时器时长达到则执行最后一次计时器中的回调逻辑。
节流:有两种,一种即时执行func,并开启节流,称为leading,一种延时执行,节流期间结束才执行,称为trailing
-
即时执行,采用时间戳方案,维护一个闭包变量lastTime=0,当lastTime为0或者now - lastTime > wait时,说明是首次触发或者已过最近一次节流时间,则立即执行func,并设置lastTime = now,否则不执行
-
延时执行,采用计时器方案,维护一个闭包变量timeout=null,当计时器wait时间已过,才执行func,并设置timeout=null,如果timeout存在则直接返回。
只有防抖会刷新timeout(clear并set) ,节流trailing方案中timeout存在则代表在节流期间,为空则代表上次节流阶段结束,可开启下次节流,timeout = setTimout(...)。
统一规范:
- 显式指定func函数的this为调用packFn的对象(防止this指向全局或undefined常量)
- 即packFn的上下文对象中的this,并统一设置变量let context = this,然后func.apply(context,args);即使箭头函数可以直接继承外层上下文(packFn)的this(这是为了规范)。
当然,由于节流函数的封装中 leading(立即执行) 和 trailing(延迟执行) 需要维护的变量以及相应逻辑不相冲突,节流还可以实现覆盖leading(时间戳方案)和trailing(计时器方案)的封装方式 ,如果觉得我本文讲得还不错,可以给个赞,想听的话评论区告诉我,咱下回分解。