浅谈节流与防抖

节流和防抖在开发项目过程中很常见,例如 input 输入实时搜索、scrollview 滚动更新了,等等,大量的场景需要我们对其进行处理。我们由 Lodash 来介绍,直接进入主题吧。

Lodash

API
  • 防抖 (debounce) :多次触发,只在最后一次触发时,执行目标函数。

    lodash.debounce(func, [wait=0], [options={}])
  • 节流(throttle):限制目标函数调用的频率,比如:1s内不能调用2次。

    lodash.throttle(func, [wait=0], [options={}])

lodash 在 opitons 参数中定义了一些选项,主要是以下三个:

  • leading:函数在每个等待时延的开始被调用,默认值为false

  • trailing:函数在每个等待时延的结束被调用,默认值是true

  • maxwait:最大的等待时间,因为如果 debounce 的函数调用时间不满足条件,可能永远都无法触发,因此增加了这个配置,保证大于一段时间后一定能执行一次函数

根据 leading 和 trailing 的组合,可以实现不同的调用效果:

  • {leading: true, trailing: false}:只在延时开始时调用

  • {leading: false, trailing: true}:默认情况,即在延时结束后才会调用函数

  • {leading: true, trailing: true}:在延时开始时就调用,延时结束后也会调用

deboucne 还有 cancel 方法,用于取消防抖动调用

使用
  • 防抖 (debounce):

    addEntity = () => {console.log('--------------addEntity---------------')this.debounceFun();}debounceFun = lodash.debounce(function(e){console.log('--------------debounceFun---------------');}, 500,{leading: true,trailing: false,})

    首次点击时执行,连续点击且时间间隔在500ms之内,不再执行,间隔在500ms之外再次点击,执行。

  • 节流(throttle):

    addEntity = () => {console.log('--------------addEntity---------------');this.throttleFun();}throttleFun = lodash.throttle(function(e){console.log('--------------throttleFun---------------');}, 500,{leading: true,trailing: false,})

    首次点击时执行,连续点击且间隔在500ms之内,500ms之后自动执行一次(注:连续点击次数时间之后小于500ms,则不会自动执行),间隔在500ms之外再次点击,执行。

源码实现

debounce
// 这个是用来获取当前时间戳的function now() {  return +new Date()}/** * 防抖函数,返回函数连续调用时,空闲时间必须大于或等于 wait,func 才会执行 * * @param  {function} func        回调函数 * @param  {number}   wait        表示时间窗口的间隔 * @param  {boolean}  immediate   设置为ture时,是否立即调用函数 * @return {function}             返回客户调用函数 */function debounce (func, wait = 50, immediate = true) {  let timer, context, args  // 延迟执行函数  const later = () => setTimeout(() => {    // 延迟函数执行完毕,清空缓存的定时器序号    timer = null    // 延迟执行的情况下,函数会在延迟函数中执行    // 使用到之前缓存的参数和上下文    if (!immediate) {      func.apply(context, args)      context = args = null    }  }, wait)  // 这里返回的函数是每次实际调用的函数  return function(...params) {    // 如果没有创建延迟执行函数(later),就创建一个    if (!timer) {      timer = later()      // 如果是立即执行,调用函数      // 否则缓存参数和调用上下文      if (immediate) {        func.apply(this, params)      } else {        context = this        args = params      }    // 如果已有延迟执行函数(later),调用的时候清除原来的并重新设定一个    // 这样做延迟函数会重新计时    } else {      clearTimeout(timer)      timer = later()    }  }}
throttle
/** * underscore 节流函数,返回函数连续调用时,func 执行频率限定为 次 / wait * * @param  {function}   func      回调函数 * @param  {number}     wait      表示时间窗口的间隔 * @param  {object}     options   如果想忽略开始函数的的调用,传入{leading: false}。 *                                如果想忽略结尾函数的调用,传入{trailing: false} *                                两者不能共存,否则函数不能执行 * @return {function}             返回客户调用函数 */_.throttle = function(func, wait, options) {    var context, args, result;    var timeout = null;    // 之前的时间戳    var previous = 0;    // 如果 options 没传则设为空对象    if (!options) options = {};    // 定时器回调函数    var later = function() {      // 如果设置了 leading,就将 previous 设为 0      // 用于下面函数的第一个 if 判断      previous = options.leading === false ? 0 : _.now();      // 置空一是为了防止内存泄漏,二是为了下面的定时器判断      timeout = null;      result = func.apply(context, args);      if (!timeout) context = args = null;    };    return function() {      // 获得当前时间戳      var now = _.now();      // 首次进入前者肯定为 true      // 如果需要第一次不执行函数      // 就将上次时间戳设为当前的      // 这样在接下来计算 remaining 的值时会大于0      if (!previous && options.leading === false) previous = now;      // 计算剩余时间      var remaining = wait - (now - previous);      context = this;      args = arguments;      // 如果当前调用已经大于上次调用时间 + wait      // 或者用户手动调了时间       // 如果设置了 trailing,只会进入这个条件      // 如果没有设置 leading,那么第一次会进入这个条件      // 还有一点,你可能会觉得开启了定时器那么应该不会进入这个 if 条件了      // 其实还是会进入的,因为定时器的延时      // 并不是准确的时间,很可能你设置了2秒      // 但是他需要2.2秒才触发,这时候就会进入这个条件      if (remaining <= 0 || remaining > wait) {        // 如果存在定时器就清理掉否则会调用二次回调        if (timeout) {          clearTimeout(timeout);          timeout = null;        }        previous = now;        result = func.apply(context, args);        if (!timeout) context = args = null;      } else if (!timeout && options.trailing !== false) {        // 判断是否设置了定时器和 trailing        // 没有的话就开启一个定时器        // 并且不能不能同时设置 leading 和 trailing        timeout = setTimeout(later, remaining);      }      return result;    };  };


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值