函数节流、函数防抖实现原理分析

4,093 阅读3分钟

之前翻译了一篇博客,里面有讲到这个,今天单独拎出来聊聊。

前言

事件的触发权很多时候都属于用户,有些情况下会产生问题:

  • 向后台发送数据,用户频繁触发,对服务器造成压力

  • 一些浏览器事件:window.onresizemousemove等,触发的频率非常高,会造成浏览器性能问题

如果你碰到这些问题,那就需要用到这些技术了。

我们先来解释一下函数节流(Throttling)和函数防抖(Debouncing)的区别:

我们上班、生活每天都需要坐电梯,用这个比喻再恰当不过了:

函数防抖和我们平时坐电梯差不多,如果有人进电梯(用户触发事件),那将在10秒钟后出发(执行程序),这时如果又有人进电梯了(用户在10秒内再次触发事件),我们又得等10秒再出发(重新计时)。

函数节流就比较直观了,有人进电梯,就开始计时,每10秒运送一次,如果没有人,则待机。

这两种策略具体使用场景还得看你的实际需求了,但是,只要理解了这个思想,接下来的就好办了。

函数节流(Throttling)

函数节流的作用上面讲的很清晰了,接下来我们分析一下如何实现它:

var throttle = function(fn, interval) { //fn为要执行的函数,interval为延迟时间
  var _self = fn,  //保存需要被延迟执行的函数引用
      timer,  //定时器
      firstTime = true;  //是否第一次调用

  return function() { //返回一个函数,形成闭包,持久化变量
    var args = arguments, //缓存变量
        _me = this;

    if(firstTime) { //如果是第一次调用,不用延迟执行
      _self.apply(_me, args);
      return firstTime = false;
    }

    if(timer) { //如果定时器还在,说明上一次延迟执行还没有完成
      return false;
    }

    timer = setTimeout(function() { //延迟一段时间执行
      clearTimeout(timer);
      timer = null;
      _self.apply(_me, args);
    }, interval || 500);
  };
};

//使用
window.onresize = throttle(function() {
  //你要执行的代码
}, 500);

其实函数节流和函数防抖的关键就是对setTimeout的运用,说个题外话,当你对setTimeoutsetInterval内部的运作原理彻底了解后,你就是一名JS大神了。😝

函数防抖(Debouncing)

我个人在开发中比较喜欢使用函数防抖策略,其实也说不上谁好,适应的场景不同。

我把注解也写在代码中了:

function debounce(fn, interval, immediate) {
  //fn为要执行的函数
  //interval为等待的时间
  //immediate判断是否立即执行
  var timeout;  //定时器

  return function() { //返回一个闭包
    var context = this, args = arguments; //先把变量缓存
    var later = function() {  //把稍后要执行的代码封装起来
      timeout = null; //成功调用后清除定时器
      if(!immediate) fn.apply(context, args); //不立即执行时才可以调用
    };

    var callNow = immediate && !timeout;  //判断是否立即调用,并且如果定时器存在,则不立即调用
    clearTimeout(timeout);  //不管什么情况,先清除定时器,这是最稳妥的
    timeout = setTimeout(later, interval);  //延迟执行
    if(callNow) fn.apply(context, args);  //如果是第一次触发,并且immediate为true,则立即执行
  };
};

//使用
var myEfficientFn = debounce(function() {
  //你要做的事
}, 250);

window.addEventListener('resize', myEfficientFn);

上面代码有一个巧妙的设计:var callNow = immediate && !timeout;,判断了timeout,如果存在,说明有定时器在运行,那就不是第一次执行,则不执行if(callNow)里的代码了。

总结

这两个代码块是在开发中经常使用的,无论是为了适应需求还是优化性能,我们都没有理由不适用它们。

另外在jQuery的源码中也使用了这种技巧,即便在这个框架横行的时代,还是只有底层的知识能让我感到踏实。

喜欢本文的朋友可以关注我的微信公众号,不定期推送一些好文。

本文出自Rockjins Blog,转载请与作者联系。否则将追究法律责任。