资讯专栏INFORMATION COLUMN

防抖和节流的实现

Carbs / 1897人阅读

摘要:防抖防抖的作用是将多个连续的调用合并为一次调用。防抖与节流之间的重要区别是,防抖是基于上次调用来计算的而节流是基于上次调用来计算的,只要距离上次调用超过了,就可以进行下次调用。实现修改自参考资料。个人认为实现更好理解。

防抖(debounce)

防抖的作用是将多个连续的debounced调用合并为一次func调用。作用见参考资料1。

两次debounced调用的间隔小于waitTime,则视为连续的调用。

如果距离上次debounced调用已经过去了waitTime的时间,则说明该轮连续调用已经结束(进入稳定状态)。这个时间点也被称为trailing edge。

在trailing edge以后的第一次debounced调用是下一轮连续调用的开始。当然,第一次debounced调用也是一轮连续调用的开始。这个时间点也被称为leading edge。

immediate参数可以控制是否在leading edge执行一次func调用。callAfterStable参数控制是否在trailing edge执行一次func调用。因此,func调用可以放在连续调用开始时,也可以放在结束时,也可以都放。一般设置immediate = false,callAfterStable = true,将func调用放在连续调用结束时。

假设debounced的调用一直持续不断,且相邻间隔都小于waitTime,则意味着连续调用一直没有结束,放在trailing edge的func调用一直不会执行。

function debounce(
  func,
  waitTime = 1000,
  immediate = false,
  callAfterStable = true
) {
  if (!immediate && !callAfterStable)
    throw new Error("immediate 和 callAfterStable 不能同时为false"); // 否则func.apply永远不会调用
  let timeout = null;
  const debounced = function(...args) {
    // timeout的值决定当前是否处于稳定状态(已经经过waitTime没有被调用了)
    // 如果已经存在一个定时器,说明现在是处于一轮连续调用当中(非稳定状态),需要重新计时
    if (timeout) clearTimeout(timeout);
    // 否则,此时是leading edge。如果配置了immediate,此时要触发func
    else if (immediate) func.apply(this, args);

    // trailing edge将在waitTime时间以后到来,进入稳定状态(前提是这段时间内没有被调用)
    timeout = setTimeout(() => {
      // 这个回调被执行时,说明已经经过waitTime没有被调用了,进入稳定状态
      timeout = null;
      // 此时是trailing edge。如果配置了callAfterStable,要触发func
      if (callAfterStable) func.apply(this, args);
    }, waitTime);
  };
  // 使用者可以调用这个函数,强行进入稳定状态
  debounced.forceStabilize = function() {
    if (timeout) {
      clearTimeout(timeout);
      timeout = null;
    }
  };
  return debounced;
}
节流(throttle)

节流的作用是限制func调用的频率(最多每waitTime调用一次)。作用见参考资料2。

防抖与节流之间的重要区别是,防抖是基于上次debounced调用来计算waitTime的;而节流是基于上次func调用来计算waitTime的,只要距离上次func调用超过了waitTime,就可以进行下次func调用。

实现2修改自参考资料2。个人认为实现1更好理解。

实现1
// immediate传入true,将在leading edge就第一次调用func
// 否则,将在 leading edge+waitTime 的时候才第一次调用func
function throttle(func, waitTime = 1000, immediate = true) {
  let timeout = null,
    // called表示自从上次func调用以后,是否是否有调用过throttled
    called,
    // 存储上一次调用throttled时提供的args和this,用来在timeExpired时调用func
    lastArgs,
    lastThis;

  function timeExpired() {
    if (called) {
      func.apply(lastThis, lastArgs);
      called = false;
      timeout = setTimeout(timeExpired, waitTime);
    } else {
      // trailing edge
      // trailing edge不调用func了,
      // 因为在waitTime之前调用过了func,且自从那以后,throttled就没有被调用过。
      timeout = null;
      // 释放内存
      lastArgs = lastThis = null;
    }
  }

  function throttled(...args) {
    lastArgs = args;
    lastThis = this;

    if (!timeout) {
      // leading edge
      if (immediate) {
        func.apply(lastThis, lastArgs);
        called = false;
      } else {
        // !immediate时,leading edge下一次的timeExpired必须调用func
        // 否则,如果在(leading edge, leading edge + waitTime]这段时间内没有调用过throttled,func一次也不会执行
        called = true;
      }
      timeout = setTimeout(timeExpired, waitTime);
    } else {
      called = true;
    }
  }

  throttled.cancle = function() {
    if (timeout) {
      clearTimeout(timeout);
      timeout = null;
      lastArgs = lastThis = null;
    }
  };

  return throttled;
}
实现2
function throttle(
  func,
  waitTime = 1000,
  immediate = true,
  callAfterStable = true
) {
  if (!immediate && !callAfterStable)
    throw new Error("immediate 和 callAfterStable 不能同时为false"); // 下面会指出原因
  let timeout = null,
    // 上一次调用func的时间
    previous = 0;
  const throttled = function(...args) {
    const now = Date.now();
    // immediate==false时,previous==0有特殊的含义:当前处于稳定状态,本次调用throttled不立即触发func
    // 阻止立即触发func的方式:previous = now,相当于0秒前刚刚调用过了func
    // 因此稳定状态下的第一次throttled调用会进入elseif,将func推迟调用
    if (!previous && !immediate) previous = now;
    const remain = waitTime - (now - previous);
    // immediate 和 callAfterStable 不能同时为false,否则if和elseif语句块都永远不会调用
    if (remain < 0 || remain > waitTime) {
      // 距离上一次调用func至少经过了waitTime,本次throttled立即触发func
      if (timeout) {
        // 有可能有timer回调仍阻塞在时间队列中(虽然肯定已经超时),销毁它
        clearTimeout(timeout);
        timeout = null;
      }
      func.apply(this, args);
      previous = now;
    } else if (!timeout && callAfterStable) {
      // throttled调用时,距离上一次调用func还没有过去waitTime,
      // 不立即触发func,而是安排到previous+waitTime时刻
      // 判断!timeout是为了防止安排多个func在previous+waitTime时刻调用
      timeout = setTimeout(() => {
        func.apply(this, args);
        // immediate==false时,previous=0表示进入稳定状态,设置它是为了阻止下一次的immediate调用
        previous = immediate ? Date.now() : 0;
        timeout = null;
      }, remain);
    }
  };
  throttled.forceStabilize = function() {
    previous = 0;
    if (timeout) {
      clearTimeout(timeout);
      timeout = null;
    }
  };
  return throttled;
}
测试代码




  
  
  test
  



  
参考资料

JavaScript专题之跟着underscore学防抖

JavaScript专题之跟着underscore学节流

Debouncing and Throttling Explained Through Examples

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/97482.html

相关文章

  • 详谈js抖和节流

    摘要:本文会分别介绍什么是防抖和节流,它们的应用场景,和实现方式。防抖和节流都是为了解决短时间内大量触发某函数而导致的性能问题,比如触发频率过高导致的响应速度跟不上触发频率,出现延迟,假死或卡顿的现象。 本文由小芭乐发表 0. 引入 首先举一个例子: 模拟在输入框输入后做ajax查询请求,没有加入防抖和节流的效果,这里附上完整可执行代码: 没有防抖 ...

    shevy 评论0 收藏0
  • 彻底弄懂函数抖和函数节流

    摘要:若时间差大于间隔时间,则立刻执行一次函数。不同点函数防抖,在一段连续操作结束后,处理回调,利用和实现。函数防抖关注一定时间连续触发的事件只在最后执行一次,而函数节流侧重于一段时间内只执行一次。 原博客地址,欢迎star 函数防抖和节流 函数防抖和函数节流:优化高频率执行js代码的一种手段,js中的一些事件如浏览器的resize、scroll,鼠标的mousemove、mouseover...

    Mr_houzi 评论0 收藏0
  • 函数抖和节流

    摘要:应用场景给按钮加函数防抖防止表单多次提交。对于输入框连续输入进行验证时,用函数防抖能有效减少请求次数。参考十分钟学会防抖和节流轻松理解函数节流和函数防抖 函数防抖和节流 防抖 对于触发非常频繁又没有必要每次都执行的事件,希望合并到一次去执行; 实现思路: 事件触发后,在规定的时间范围内如果事件重复触发,那么忽略之前触发的事件,并且重新开始计时,直到某一次事件触发后大于规定时间,我们才执...

    daydream 评论0 收藏0
  • 函数抖和节流是个啥???

    摘要:函数防抖和节流,都是控制事件触发频率的方法。封装一个函数,让持续触发的事件监听是我们封装的这个函数,将目标函数作为回调传进去,等待一段时间过后执行目标函数第二点实现了,再看第一点持续触发不执行。 曾经面试时候被问到过这个,年少的我一脸无知。。。 后来工作中遇到了一个场景:输入名称的同时去服务器校验名称是否重复,但发现之前的代码竟然都没做限制,输入一次发一次请求。简直忍不了,就在项目的u...

    edagarli 评论0 收藏0
  • 性能-抖和节流

    摘要:一概述为了提高页面性能,有时需要对高频率触发的事件进行防抖或者节流处理。强调一系列连续触发的事件。防抖的目的就是把,,构成的系列或者,构成的系列事件合并成一个,即只执行或者或者。节流一段时间内的事件,只处理一次,即只调用一次事件处理程序。 一、概述 为了提高页面性能,有时需要对高频率触发的事件(scrllo, resize, mousemove, touchmove)进行防抖(Debo...

    zhjx922 评论0 收藏0
  • 性能-抖和节流

    摘要:一概述为了提高页面性能,有时需要对高频率触发的事件进行防抖或者节流处理。强调一系列连续触发的事件。防抖的目的就是把,,构成的系列或者,构成的系列事件合并成一个,即只执行或者或者。节流一段时间内的事件,只处理一次,即只调用一次事件处理程序。 一、概述 为了提高页面性能,有时需要对高频率触发的事件(scrllo, resize, mousemove, touchmove)进行防抖(Debo...

    piglei 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<