理解函数防抖与函数节流

302 阅读4分钟

函数节流与函数防抖

函数防抖 debounce

定义

一个事件持续被触发,我们需要定一段时间,在这段时间之内,该事件没有被触发,我们就执行这个事件的处理函数,如果在这段时间内,事件又被触发了,就从新开始计时

理解

不停的快速按a键 只显示一个a

就像游戏中放一个延时技能,你如果再继续按一次,那这个延时技能重新开始计时,当这段时间过后,没有重新触发,则技能释放成功

就像你坐电梯 5秒准备关门了 等了4秒 这时来了一个人 又要重新等5秒 才能关门

应用

  • search搜索联想,用户在不断输入值时,用防抖来节约请求资源。
  • window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次

工具函数 debounce执行过程

  1. debounce函数返回一个函数,
  2. 把这个内部函数 代替原函数放进事件处理函数中,每次事件触发时,都会先清除定时器,第一次清除是没用的,但是当第二次触发时,这个内部函数每一次清除定时 都会阻止前一次的定时器里的回调被调用,原函数就不会被触发
  3. 只有当高频事件停止触发的时候,最后一次事件触发才能真正把定时器的回调在delay时间之后放入异步队列去等待执行
//函数防抖的工具函数
function debounce(fn,delay){
  return function (arg){
    let that = this
    let _arg = arg
    clearTimeout(fn.timer)
    fn.timer = setTimeout(() => fn.call(that,_arg),delay)
  }
}

函数节流 throttle

定义

规定一段时间内,只能触发一次这个函数。如果在这段时间内被事件触发多次这个,那么也就只能生效一次

这个函数就是事件处理函数

理解

就比如 你不停的按a键,但是每几秒 才只能打出一个a

fps游戏中,你就算把鼠标按烂了 ,狙击枪的射速也就那么快 ,几秒才能打一枪

应用

  • 鼠标不断点击触发,mousedown(单位时间内只触发一次)
  • 监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断

工具函数 throttle 执行过程

1 当第一次触发事件时,肯定不会立即执行函数,而是在delay秒后才执行。因为flag是true,所以可以把定时器里的回调放入线程池中。但是你在delay时间之内再次触发事件,因为flag是false,所以直接结束了这个函数。

2 当delay时间到了之后,flag再次被修改为true,这样就可以再次把这个内部函数执行完整了。以此类推 之后连续不断触发事件,也会每delay秒执行一次。

3 当最后一次停止触发后,由于定时器的delay延迟,可能还会执行一次函数。

//函数节流 的 工具函数
function throttle(fn,delay){
  let flag = true
  return function (arg){
    if(!flag) return
    flag = false
    let that = this
    let _arg = arg
    setTimeout(() => {
      fn.call(that,_arg)
      flag = true
    },delay)
  }
}

总结

​ 1 函数防抖和函数节流都是防止某一时间频繁触发,

​ 2 函数防抖是某一段时间内只执行一次,而函数节流是间隔时间执行。

扩展

函数节流也可以用时间戳来实现

function throttle(func, delay){
  let prev = Date.now();
  return function(){
    const context = this;
    const args    = arguments;
    const now     = Date.now();
    if(now - prev >= delay){
      func.apply(context, args);
      prev = Date.now();
    }
  }
}
当高频事件触发时,第一次应该会立即执行(给事件绑定函数与真正触发事件的间隔如果大于delay的话),而后再怎么频繁触发事件,也都是会每delay秒才执行一次。而当最后一次事件触发完毕后,事件也不会再被执行了。

综合使用时间戳与定时器,完成一个事件触发时立即执行,触发完毕还能执行一次的节流函数

function throttle(func, delay){
  let timer = null;
  let startTime = Date.now();

  return function(){
    let curTime = Date.now();
    let remaining = delay - (curTime - startTime);
    const context = this;
    const args = arguments;

    clearTimeout(timer);
    if(remaining <= 0){
      func.apply(context,args);
      startTime = Date.now();
    }else{
      timer = setTimeout(func, remaining);
    }
  }
}

需要在每个delay时间中一定会执行一次函数,因此在节流函数内部使用开始时间、当前时间与delay来计算remaining,当remaining <= 0时表示该执行函数了,如果还没到时间的话就设定在remaining时间后再触发。当然在remaining这段时间中如果又一次发生事件,那么会取消当前的计时器,并重新计算一个remaining来判断当前状态。

参考文章

作者:亖巠链接:juejin.cn/post/684490…

薄荷前端周刊 juejin.cn/post/684490…