节流与防抖

280 阅读3分钟

有些对象需绑定一些持续触发的事件(比如滚动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()    //五秒后调用 Hiconst 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

使用场景

  • 节流:用在频繁点击按钮,不希望频繁触发事件,设置五秒内只能点一次。
  • 防抖:频繁拖动改变页面大小,希望在拖动停止时再实现效果,防抖使其只触发一次,节约资源。

比较

  • 共同点:都是为了防止某一时间内的频繁触发。
  • 不同点:节流是间隔时间执行,重新调用打断的那次不执行;防抖是某段时间只执行一次,有后续调用打断就重新开始执行当前这一次。示意图如下:

1.PNG