浅谈节流与防抖

节流和防抖在开发项目过程中很常见,例如 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;
    };
  };

走在最后,欢迎 star:https://github.com/sisterAn/blog

欢迎关注:前端瓶子君

原文链接:segmentfault.com

上一篇:promise-queue
下一篇:rdf-data-model

相关推荐

  • 防抖(debounce)和节流(throttle)的学习总结

    防抖(debounce) 用户与网页进行交互时,经常出现根据页面的状态、数据向服务器请求、发送数据的场景,比如:根据用户的输入数据进行实时校验,下拉请求数据等等,如果用户操作过于频繁,页面状态、数...

    1 年前
  • 防抖(debounce)和节流(throttle)---解决事件频繁触发造成页面卡死

    连续触发(触发频率很高)的时间,不进行优化,会出现页面卡顿现象。 常见的需要优化的事件: 鼠标事件: mousemove(拖拽) mouseover(划过) ...

    1 年前
  • 防抖和节流的实现、区别与应用场景

    应用: 在我们开发过程中,会有一些用户操作,如滚动事件,输入事件等导致一直重复调用某函数,频率无限,使浏览器负担过重,导致页面卡顿。这时候就会用到防抖和节流来控制调用频率,减少浏览器负担。

    7 个月前
  • 防抖和节流的实现

    防抖(debounce) 防抖的作用是将多个连续的调用合并为一次调用。作用见参考资料1。 1. 两次调用的间隔小于,则视为连续的调用。 2. 如果距离上次调用已经过去了的时间,则说明该轮连续...

    2 年前
  • 防抖和节流(较全&可体验)

    (https://img.javascriptcn.com/3c2d27a3a2e77b5c149efb2cd4a1b942) 防抖(debounce) 概念 事件被触发经过单位时间(del...

    7 个月前
  • 防抖动处理和节流 小技巧

    1. 简单的防抖动处理,一秒内点击一次 2. 向服务器请求数据 点击按钮向后台请求数据 优化点: 另外一些防抖动的小技巧,请参考: http://blog.csdn.net/crys...

    2 年前
  • 防抖与节流(源码学习)

    最近自己撸了一个轮播图,在点击切换的时候,为了寻求更好的用户体验,引入了节流,在此记录对源码的学习过程 源码来源:underscore(https://github.com/jashkenas/und...

    2 年前
  • 防抖、节流

    防抖(debounce) n秒内执行多次同一函数只有最后一次有效。 实现方式:使用计时器,在 n 秒内再执行该函数,如果 n 秒内再次调用该函数,则重置计时器。 节流(throttle) n秒内执行多...

    3 天前
  • 重拾JS——防抖与节流

    防抖 与 节流 是前端在优化性能问题上,经常使用的两种技术手段。比如 input,scroll,resize,mousemove 等事件,如果不加以控制,频繁的触发,无疑将会带来额外的性能开销,极端情...

    1 个月前
  • 说说JavaScript中函数的防抖 (Debounce) 与节流 (Throttle)

    为何要防抖和节流 有时候会在项目开发中频繁地触发一些事件,如 、 、 、 等,或者诸如输入框的实时搜索功能,我们知道如果事件处理函数无限制调用,会大大加重浏览器的工作量,有可能导致页面卡顿影响体验...

    1 年前

官方社区

扫码加入 JavaScript 社区