前端开发的函数防抖与函数节流

版权声明:本文为博主原创文章,仅供学习交流,未经博主允许不得转载。 https://blog.csdn.net/zjy_android_blog/article/details/82502690

最近在开发中遇到一个问题,如果在很短的事件内连续点击同一个按钮,按钮的事件会触发多次,在网上查了一下资料发现undescore.js 这个工具插件,里面提供了这样两个函数 debounce 和  throttle ;underscore1.8.2的文档是这样的:

一、throttle(节流)

 _.throttle(function, wait, [options])  option的值是 {leading:false,trailing:false
创建并返回一个像节流阀一样的函数,当重复调用函数的时候,最多每隔 wait毫秒调用一次该函数。对于想控制一些触发频率较高的事件有帮助。(愚人码头注:详见:javascript函数的throttle和debounce

默认情况下,throttle将在你调用的第一时间尽快执行这个function,并且,如果你在wait周期内调用任意次数的函数,都将尽快的被覆盖。如果你想禁用第一次首先执行的话,传递{leading: false},还有如果你想禁用最后一次执行的话,传递{trailing: false}。

var throttled = _.throttle(updatePosition, 100);
$(window).scroll(throttled);

以上只是给出了用法,下面来解析一下源码:

// 获取当前时间的毫秒数,当然你通过 +new Date()也可以直接获取到当前时间毫秒数
var now = Date.now || function() {
    return new Date().getTime();
};
var throttle = function(func, wait, options) {
    
    var context, args, result;  // 上下文this, 函数func的参数, 函数func的执行结果
    var timeout = null;         // 定时器
    var previous = 0;           // 上次事件func的执行时间毫秒数 
 
    // 可以传入的值是 options.leading,options.trailing,都是布尔类型
    if (!options) options = {};
    
    // 这是一个定时器任务函数(可以先不看这个,看完下面的代码再来看这个函数)
    var later = function() {
      // 如果 options.leading 为 false , 上次事件执行时间赋值为0
      previous = options.leading === false ? 0 : now();
      // 定时器置为null
      timeout = null;  
      // 传递上下文参数并执行
      result = func.apply(context, args);
      if (!timeout) context = args = null;
    };
    // 返回一个函数
    return function() {
      // 保存当前上下文
      context = this;
      // 保存当前参数
      args = arguments;
      
      var now = now(); // 当前时间毫秒数
      // 更新执行func的时间previous,并禁用func第一次首先执行
      if (!previous && options.leading === false) previous = now;
      // 还剩下多少延迟时间
      var remaining = wait - (now - previous);

      // remaining <= 0已经足够证明已经到达wait的时间间隔,但这里还考虑到假如客户端修改了系统时间则马上执行func函数。
      if (remaining <= 0 || remaining > wait) {
        // 如果定时器存在,清除定时器
        if (timeout) {
          clearTimeout(timeout);
          timeout = null;
        }
        // 将当前时间重新赋值给previous
        previous = now;
        // 执行函数func,将结果保存在result
        result = func.apply(context, args);
        // 如果定时器不存在就清除上下文,我认为这里是为了防止闭包造成的内存泄漏
        if (!timeout) context = args = null;
      } else if (!timeout && options.trailing !== false) {
        // 没有定时器任务,而且不禁用最后一次调用函数
        timeout = setTimeout(later, remaining);
      }
      return result;
    };
  };

二、debounce(防抖)

 _.debounce(function, wait, [immediate]) 
返回 function 函数的防反跳版本, 将延迟函数的执行(真正的执行)在函数最后一次调用时刻的 wait 毫秒之后. 对于必须在一些输入(多是一些用户操作)停止到达之后执行的行为有帮助。 例如: 渲染一个Markdown格式的评论预览, 当窗口停止改变大小之后重新计算布局, 等等.

传参 immediate 为 true, debounce会在 wait 时间间隔的开始调用这个函数 。(愚人码头注:并且在 waite 的时间之内,不会再次调用。)在类似不小心点了提交按钮两下而提交了两次的情况下很有用。 (感谢 @ProgramKid 的翻译建议)

var lazyLayout = _.debounce(calculateLayout, 300);
$(window).resize(lazyLayout);

下面来解析一下源码:

// 获取当前时间的毫秒数,当然你通过 +new Date()也可以直接获取到当前时间毫秒数
var _now = Date.now || function () {
  return new Date().getTime();
};
var _debounce = function (func, wait = 300, immediate) {
  // 定时器,函数func的参数,上下文this, 上一个函数被返回的时间戳, 函数func的执行结果
  var timeout, args, context, timestamp, result;

  // 这是一个定时器任务函数(可以先不看这个,看完下面的代码再来看这个函数)
  var later = function() {
    // 该定时器时间生成的时间戳与上一函数的时间戳的差值
    var last = _now() - timestamp;
    // 差值在0~wait之间,就重新设置定时器,否则判断是否已经执行过一次了,没有执行过就执行一次该函数
    if (last < wait && last >= 0) {
      timeout = setTimeout(later, wait - last);
    } else {
      timeout = null;
      if (!immediate) {
        result = func.apply(context, args);
        // 释放变量
        if (!timeout) context = args = null;
      }
    }
  };

  return function() {
    context = this;     // 保存上下文对象
    args = arguments;   // 保存参数
    timestamp = _now(); // 保存当前时间戳
    // 获取立即执行的判定值,如果用户设定立即执行,并且之前没有定时器
    var callNow = immediate && !timeout;
    // 设置定时器
    if (!timeout) timeout = setTimeout(later, wait);
    // 如果符合立即执行的条件就马上执行一次函数
    if (callNow) {
      result = func.apply(context, args);
      // 释放变量
      context = args = null;
    }
    return result;
  };
};

以上是本人的见解,如果有错误,还请您在评论中留言。

猜你喜欢

转载自blog.csdn.net/zjy_android_blog/article/details/82502690
今日推荐