有些对象需绑定一些持续触发的事件(比如滚动scroll),我们并不希望在事件持续触发的过程中那么频繁地去执行函数,「防抖」和「节流」是比较好的解决方案。
计时器 setTimeout
用法为setTimeout(fn,时间),返回值是一个整型数值id,作为该setTimeout唯一标识符,clearTimeout(id)用来取消setTimeout计时器。
let timeID=setTimeout(f,time); //1525
clearTimeout(timeID); //取消了,不用一段时间后执行fn
节流 throttle
就是「技能冷却中」。调用一个技能,在设定的时间间隔内多次调用,只有一次生效。
设置一个 是否在冷却中状态:
- 如果初始状态在冷却中,直接退出
- 如果初始状态不在冷却中,那就执行技能函数,并置为冷却,等xx秒后再置为解冻。
const skill=()=>{ //技能实现功能
console.log('Hi')
}
let flag=false
let timeID=null
function throttle(){ //发动技能函数
if(flag===true)
return
skill()
flag=true
timeID=setTimeout(()=>{ flag=false },120*1000)
}
缺点:以上代码只能实现,用发动技能函数 f1 去发动 skill 函数的技能。
能否实现用一个通用的函数 throttle ,可以指定任何的技能函数 fn 和冷却时间 time ?
const throttle = (fn, time) => { //fn是技能函数,time为设置的冷却时间
let flag = false
let timeID=null //setTimeout执行的标识符
return (...args) => {
if(flag)
return
fn.call(undefined, ...args) //调用fn,若有参数,就将参数作为数组 等同于fn(...args)
flag = true
timeID=setTimeout(()=>{ flag = false }, time)
}
}
简洁版:直接使用计时器标识符 timeID去代替冷却状态flag 判断:
const throttle = (f, time) => {
let timeID = null
return (...args) => {
if(timeID) //如果timer有值的
return
f.call(undefined, ...args) //或者f.apply(undefined,args)
timeID = setTimeout(()=>{
timeID = null //时间到了就变成空
}, time)
}
}
使用方法
const skill1=()=>{ console.log('Hi'); } // 有传参
const skill2=(a,b)=>{ console.log(a+b); } // 无传参
const f1=throttle(skill1,5000)
f1() // Hi
f1() //立即调用 undefined
f1() //五秒后调用 Hi
const f2=throttle(skill2,5000)
f2(1,2) //3 args就是[1,2]
f2(1,2) //undefined
防抖 debounce
就是「技能被打断」,如果技能在「设定的时间间隔内」再次执行会失败,触发重新计时。
设置一个计时器标识符ID,初始为null:
- 如果ID是整数,说明已经有技能正在执行,取消打断此次正在执行的技能
- 如果ID为null:说明当前没在执行技能,就设定setTimeout在一段时间之后执行这个技能,执行后继续将计时器ID置为null
const skill=()=>{
console.log('回城成功!')
}
let timeID=null
function debounce(){
if(timeID){ //再次调用x
clearTimeout(timeID)
}
timeID=setTimeout(()=>{
skill()
timer=null
},3000)
}
改写为通用版本:
const debounce = (fn, time) => {
let timeID = null
return (...args)=>{
if(timeID !== null)
clearTimeout(timeID) // 打断回城
// 重新回城
timeID = setTimeout(()=>{
fn.call(undefined, ...args) // 回城后调用 fn
timeID = null
}, time)
}
}
使用方法
const f1 = debounce(()=>console.log(1), 3000)
f1() //3秒后输出 1
f1() //技能被打断 undefined
使用场景
- 节流:用在频繁点击按钮,不希望频繁触发事件,设置五秒内只能点一次。
- 防抖:频繁拖动改变页面大小,希望在拖动停止时再实现效果,防抖使其只触发一次,节约资源。
比较
- 共同点:都是为了防止某一时间内的频繁触发。
- 不同点:节流是间隔时间执行,重新调用打断的那次不执行;防抖是某段时间只执行一次,有后续调用打断就重新开始执行当前这一次。示意图如下: