debounce & throttle

  • lodash
    • API
        _.debounce(func, [wait=0], [options={}])
        func (Function): 要防抖动的函数
        [wait=0] (number): 需要延迟的毫秒数
        [options={}] (Object): 选项对象
        [options.leading=false] (boolean): 指定在延迟开始前调用默认false
        [options.maxWait] (number): 设置 func 允许被延迟的最大值
        [options.trailing=true] (boolean): 指定在延迟结束后调用默认true
      
        _.throttle(func, [wait=0], [options=])
        func (Function): 要节流的函数
        [wait=0] (number): 需要节流的毫秒
        [options=] (Object): 选项对象
        [options.leading=true] (boolean): 指定调用在节流开始前
        [options.trailing=true] (boolean): 指定调用在节流结束后
      

      lodash debounce & throttle

  • underscore
    • API
        _.debounce(func, wait, [immediate])
        func (Function): 要节流的函数
        [wait=0] (number): 需要节流的毫秒
        [immediate=false] (number): 第一次是否立即执行
      
        _.throttle(func, wait, [options])
        func (Function): 要节流的函数
        [options=] (Object): 选项对象
        [options.leading=true] (boolean): 指定调用在节流开始前
        [options.trailing=true] (boolean): 指定调用在节流结束后
      

debounce

  • 为什么需要使用
    • 使用场景:
      • mouse move
      • 在用户输入完成时进行字符串校验
      • size/scroll的触发统计事件,可以通过debounce合并ajax请求事件
  • debounce思路
    • 是什么: 点击结束之后等delay(5s)的时间,然后触发函数,是一种结果的状态,原理就是在闭包内维护一个setTimeout定时器

      • c(click) , t(trigger)
      • ccccccccccc12345t—–cccccccc123c12345t—
    • lodash思路

      • 检查是不是requestAnimationFrame, 这个和debounce有点类似,不过间隔是下一个page paint的时候跑,不是debounce这样指定固定时间,其实我们再debounce的时间的时候会尽可能设置为刷新屏幕的时间,比如66ms,来尽可能的提高性能.这里判断的逻辑就是const useRAF = (!wait && wait !== 0 && typeof root.requestAnimationFrame === 'function')
      • 强制转成数字或者默认的0,wait = +wait || 0保证wait一定是个数字,如果不是默认也是0,还挺有意思
      • 强制转成true/false,比如: leading = !!leading
      • 因为lastCallTime === undefined 并且 timerId === undefined,所以先执行 leadingEdge,
      • leading = true,就会执行 func。同时,这里会设置一个定时器,在等待 wait(s) 后会执行 timerExpired,timerExpired 的主要作用就是触发 trailing。
      • 如果在还未到 wait 的时候就再次调用了函数的话,会更新 lastCallTime,并且因为此时 isInvoking 不满足条件,所以这次什么也不会执行。
      • 时间到达 wait 时,就会执行我们一开始设定的定时器timerExpired,此时因为time-lastCallTime < wait,所以不会执行 trailingEdge。
      • 这时又会新增一个定时器,下一次执行的时间是 remainingWait,如果没有 maxwait,定时器的时间是 wait - timeSinceLastCall,保证下一次 trailing 的执行,这是默认情况,如果指定了maxing,会比较出下一次 maxing 和下一次 trailing 的最小值,作为下一次函数要执行的时间。
    • underscore

      • underscore的思路
        • 既然和时间有关,那就控制时间来确定什么时候执行,这个是underscore的思路
          var later = function() {
            var passed = now() - previous;
            if (wait > passed) {
              timeout = setTimeout(later, wait - passed);
            } else {
              timeout = null;
              if (!immediate) result = func.apply(context, args);
              // This check is needed because `func` can recursively invoke `debounced`.
              if (!timeout) args = context = null;
            }
          };
          
          var debounced = function(_args) {
            context = this;
            args = _args;
            previous = now();
            if (!timeout) {
              timeout = setTimeout(later, wait);
              if (immediate) result = func.apply(context, args);
            }
            return result;
          };
          

throttle

  • throttle的思路
    • lodash的思路,就是直接用debounce来,指定leading, trailing就可以
    function throttle(func, wait, options) {
      let leading = true
      let trailing = true
    
      if (typeof func !== 'function') {
        throw new TypeError('Expected a function')
      }
      if (isObject(options)) {
        leading = 'leading' in options ? !!options.leading : leading
        trailing = 'trailing' in options ? !!options.trailing : trailing
      }
      return debounce(func, wait, {
        leading,
        trailing,
        'maxWait': wait
      })
    }
    
    • underscore的思路
    export default function throttle(func, wait, options) {
      var timeout, context, args, result;
      var previous = 0;
      if (!options) options = {};
    
      var later = function() {
        previous = options.leading === false ? 0 : now();
        timeout = null;
        result = func.apply(context, args);
        if (!timeout) context = args = null;
      };
    
      var throttled = function() {
        var _now = now();
        if (!previous && options.leading === false) previous = _now;
        var remaining = wait - (_now - previous);
        context = this;
        args = arguments;
        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) {
          timeout = setTimeout(later, remaining);
        }
        return result;
      };
      return throttled;
    }