zoukankan      html  css  js  c++  java
  • lodash源码学习debounce,throttle

    函数去抖(debounce)和函数节流(throttle)通常是用于优化浏览器中频繁触发的事件,具体内容可以看这篇文章http://www.cnblogs.com/fsjohnhuang/p/4147810.html

    直接看lodash中对应方法的实现

    _.debounce(func, [wait=0], [options={}])

    //debounce.js
    
    var isObject = require('./isObject'),//是否是对象
        now = require('./now'),//获取当前时间
        toNumber = require('./toNumber');//转为为数字
    
    
    var FUNC_ERROR_TEXT = 'Expected a function';
    
    var nativeMax = Math.max,//原生最大值方法
        nativeMin = Math.min;//原生最小值方法
    
    /**
     * 函数去抖,也就是说当调用动作n毫秒后,才会执行该动作,若在这n毫秒内又调用此动作则将重新计算执行时间。
     *
     * @param {Function} func 需要去抖的函数.
     * @param {number} [wait=0] 延迟执行的时间.
     * @param {Object} [options={}] 选项对象.
     * @param {boolean} [options.leading=false] 指定是否在超时前调用.
     * @param {number} [options.maxWait] func延迟调用的最大时间.
     * @param {boolean} [options.trailing=true] 指定是否在超时后调用.
     * @returns {Function} 返回去抖之后的函数.
     * @example
     *
     * // Avoid costly calculations while the window size is in flux.
     * jQuery(window).on('resize', _.debounce(calculateLayout, 150));
     *
     * // Invoke `sendMail` when clicked, debouncing subsequent calls.
     * jQuery(element).on('click', _.debounce(sendMail, 300, {
     *   'leading': true,
     *   'trailing': false
     * }));
     *
     * // Ensure `batchLog` is invoked once after 1 second of debounced calls.
     * var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 });
     * var source = new EventSource('/stream');
     * jQuery(source).on('message', debounced);
     *
     * // Cancel the trailing debounced invocation.
     * jQuery(window).on('popstate', debounced.cancel);
     */
    function debounce(func, wait, options) {
      var lastArgs,    //上次调用参数
          lastThis,    //上次调用this
          maxWait,    //最大等待时间
          result,    //返回结果
          timerId,    //timerId
          lastCallTime,    //上次调用debounced时间,即触发时间,不一定会调用func
          lastInvokeTime = 0, //上次调用func时间,即成功执行时间
          leading = false,    //超时之前
          maxing = false,     //是否传入最大超时时间
          trailing = true;    //超时之后
    
      if (typeof func != 'function') {
        throw new TypeError(FUNC_ERROR_TEXT);
      }
      wait = toNumber(wait) || 0;
      if (isObject(options)) {
        leading = !!options.leading;
        maxing = 'maxWait' in options;
        maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait;
        trailing = 'trailing' in options ? !!options.trailing : trailing;
      }
    
      function invokeFunc(time) {//调用func,参数为当前时间
        var args = lastArgs,//调用参数
            thisArg = lastThis;//调用的this
    
        lastArgs = lastThis = undefined;//清除lastArgs和lastThis
        lastInvokeTime = time;    //上次调用时间为当前时间
        result = func.apply(thisArg, args);//调用func,并将结果返回
        return result;
      }
    
      function leadingEdge(time) {//超时之前调用
        lastInvokeTime = time;//设置上次调用时间为当前时间
        timerId = setTimeout(timerExpired, wait); //开始timer
        return leading ? invokeFunc(time) : result;//如果leading为true,调用func,否则返回result
      }
    
      function remainingWait(time) {//设置还需要等待的时间
        var timeSinceLastCall = time - lastCallTime,//距离上次触发的时间
            timeSinceLastInvoke = time - lastInvokeTime,//距离上次调用func的时间
            result = wait - timeSinceLastCall;//还需要等待的时间
    
        return maxing ? nativeMin(result, maxWait - timeSinceLastInvoke) : result;
      }
    
      function shouldInvoke(time) {//是否应该被调用
        var timeSinceLastCall = time - lastCallTime,//距离上次触发时间的时间
            timeSinceLastInvoke = time - lastInvokeTime;//距离上次调用func的时间
    
       
        return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
          (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait));
      }
    
      function timerExpired() {//刷新timer
        var time = now();
        if (shouldInvoke(time)) {//如果可以调用,调用trailingEdge
          return trailingEdge(time);
        }
        timerId = setTimeout(timerExpired, remainingWait(time));//不调用则重置timerId
      }
    
      function trailingEdge(time) {//超时之后调用
        timerId = undefined;
    
        // Only invoke if we have `lastArgs` which means `func` has been
        // debounced at least once.
        if (trailing && lastArgs) {//如果设置trailing为true,并且有lastArgs,调用func
          return invokeFunc(time);
        }
        lastArgs = lastThis = undefined;//清除lastArgs和lastThis
        return result;//否则返回result
      }
    
      function cancel() {//取消执行
        if (timerId !== undefined) {
          clearTimeout(timerId);
        }
        lastInvokeTime = 0;
        lastArgs = lastCallTime = lastThis = timerId = undefined;
      }
    
      function flush() {//直接执行
        return timerId === undefined ? result : trailingEdge(now());
      }
    
      function debounced() {
        var time = now(),
            isInvoking = shouldInvoke(time);//判断是否可以调用
    
        lastArgs = arguments;//得到参数
        lastThis = this;//得到this对象
        lastCallTime = time;//触发时间
    
        if (isInvoking) {
          if (timerId === undefined) {//首次触发,调用leadingEdge
            return leadingEdge(lastCallTime);
          }
          if (maxing) {
            // 处理多次频繁的调用
            timerId = setTimeout(timerExpired, wait);//设置定时器
            return invokeFunc(lastCallTime);
          }
        }
        if (timerId === undefined) {//如果没有timer,设置定时器
          timerId = setTimeout(timerExpired, wait);
        }
        return result;//返回result
      }
      debounced.cancel = cancel;
      debounced.flush = flush;
      return debounced;
    }
    
    module.exports = debounce;

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

    //throttle.js
    
    var debounce = require('./debounce'),//debounce方法
        isObject = require('./isObject');//判断是否为对象
    
    var FUNC_ERROR_TEXT = 'Expected a function';
    
    /**
     * 函数节流
     *
     * @param {Function} func 需要处理的函数.
     * @param {number} [wait=0] 执行间隔.
     * @param {Object} [options={}] 选项对象.
     * @param {boolean} [options.leading=false] 指定是否在超时前调用.
     * @param {number} [options.maxWait] func延迟调用的最大时间.
     * @param {boolean} [options.trailing=true] 指定是否在超时后调用.
     * @returns {Function} 返回节流之后的函数.
     * @example
     *
     * // Avoid excessively updating the position while scrolling.
     * jQuery(window).on('scroll', _.throttle(updatePosition, 100));
     *
     * // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes.
     * var throttled = _.throttle(renewToken, 300000, { 'trailing': false });
     * jQuery(element).on('click', throttled);
     *
     * // Cancel the trailing throttled invocation.
     * jQuery(window).on('popstate', throttled.cancel);
     */
    function throttle(func, wait, options) {
      var leading = true,
          trailing = true;
    
      if (typeof func != 'function') {
        throw new TypeError(FUNC_ERROR_TEXT);
      }
      if (isObject(options)) {
        leading = 'leading' in options ? !!options.leading : leading;
        trailing = 'trailing' in options ? !!options.trailing : trailing;
      }
      return debounce(func, wait, {
        'leading': leading,
        'maxWait': wait,
        'trailing': trailing
      });
    }
    
    module.exports = throttle;

    可以看到这两个方法基本上都差不多,区别在于throttle初始的时候设置了leading为true和maxWait,这样和debounce的区别在于,在第一次触发的时候throttle会直接调用,并且每隔wait的时间都会调用一次,而debounce第一次不会调用,并且只有当触发的间隔时间大于wait时才会调用,否则一直不会调用。

  • 相关阅读:
    快速莫比乌斯变换(FMT)和快速沃尔什变换(FWT)
    组合意义证明——「Zeilberger 老爷子的 T 恤上写了啥?」
    【题解】礼物 | [洛谷 P4916] 魔力环【20210113 省选模拟赛】【Burnside引理 容斥 组合数】
    线性基总结
    HTML中的img标签属性
    html5语义化标签
    html中代替空格、大于号、小于号等字符编码
    前端网页全局属性
    js中的script标签属性
    html中的meta标签属性
  • 原文地址:https://www.cnblogs.com/wandiao/p/7223269.html
Copyright © 2011-2022 走看看