函数防抖


什么是函数函数防抖

函数防抖: 其实概念很简单,就两句话。
第一句:函数执行过一次后,在等待某时间段内不能再次执行
第二句: 在等待时间内触发此函数,则重新计算等待时间
这两句话既是函数防抖的定义,也是函数防抖的原理。lodash中的_.bounce就是围绕这两句话写的, 后面会给大家讲解。

换句通俗的话说,就是:你尽管触发事件,但是我一定在事件触发n秒后才执行,如果你在一个事件触发的n秒内又触发了这个事件,那我就以新的事件的时间为准,n秒后才执行,总之,就是要等你触发完事件n秒内不再触发事件,我才执行,真是任性呐。

如果不理解上面定义,我可以再给大家举个生活例子:
如果有人进电梯(触发事件),那电梯将在10s出发(执行事件句柄),这时如果又有人进电梯了(在10s内再次触发该事件),我们又得等10s再出发(重新计时)。这个例子很完美的展示了函数防抖的精髓。

函数防抖使用场景

现在,大家可以很容易理解什么是函数防抖了吧。结合刚才的生活例子,可以想象下,在我们前端界有哪些是函数防抖的使用场景:

  • 每次 resize/scroll 触发统计事件
  • 文本输入验证(连续输入文字后发生AJAX请求进行验证,验证一次就好)
  • mousemove,mousedown
  • 加载更多(这个与前2个还是有点区别)

函数防抖的封装:

第一版:

我们先以函数被执行过一次后,在等待某时间段内不能再次执行原理,封装一个简单的函数防抖:
先假设函数的定义形式为:

window.onscroll = _debounce(function(){
  console.log(1);
}, 500);
function _debounce(func, wait) {
  var timer = null;
  var context, args;
  return function () {
     context = this; 
     args = arguments;
    clearTimeout(timer);
    timer = setTimeout(function() {
      func.apply(context, args);
    }, wait);
  }
}

通过调用_debounce, 返回一个闭包函数。在连续调用_debounce时,只会在最后一次触发的时候执行回调,这是因为每次调用, 都会通过clearTimeout会清掉上一个timer。

现在仔细想想,这个封装貌似有个问题:如果我想在第一次触发的时候执行回调呢?这种场景是不是很适合加载更多场景?因为从体验方面看,我们希望第一次触发加载更多的时候,就执行回调。而不是在最后一次触发的时候执行回调。如果我们一直滚动页面,那加载更多就一直不触发...这种体验是不是很糟糕。

基于上面问题,我们再次进行封装,多添加一个参数。
现假设函数的定义形式为:

window.onscroll = _debounce(function(){
    console.log(1);
}, 500, true);
function _debounce(func, wait, immediate) {
    var timer = null;
    var context, args, result, timestamp;

    var later = function() {
        var oDate = new Date();
        var last = oDate.getTime() - timestamp; // 计算第一次时间戳与当前时间戳的差值。

        if (last < wait && last >= 0) { // 在等待时间内触发此函数,重新计时。
            timer = setTimeout(later, wait - last);
        } else {
            timer = null;
            if(!immediate) { // 限制immediate 为true时,执行回调函数。
                result = func.apply(context, args);
                if(!timer) context = args = null;
            }
        }
    }

    return function() {
        var oDate = new Date();  
        var callNow = immediate && !timer; // 代表第一次调用立即执行。
      
        timestamp = oDate.getTime(); // 记录下当前时间戳
        context = this; // 保存上下文
        args = arguments; 
    
        if(!timer) { // 第一次触发时,timer为空,进入此分支
            timer = setTimeout(later, wait);
        }

        if(callNow) { // 第一次触发且immediate为true,进入此分支
            result = func.apply(context, args);
            context = args = null;  
        }
        
        return result;
    }
}

此次封装支持两种形式:
immediate为true时,连续事件触发时,只会在第一次触发的时候执行回调。
immediate为false时,连续事件触发时,只会在最后一次触发的时候执行回调。

参考文献:
https://github.com/mqyqingfeng/Blog/issues/22

你可能感兴趣的:(函数防抖)