跟着underscore学防抖

614 阅读4分钟

本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。

1. 引言

前端中存在很多频繁的事件触发,如:resizeonChange…等,若事件处理回调函数包含网络请求等操作,那么过于频繁的IO操作会导致页面产生性能问题,因此加入防抖策略以可以在一定程度上改善页面性能。

下面就是通过阅读underscore源码,学习防抖的原理以及如何实现一个简单的防抖程序。

2. 源码

源码仓库位于: github

debounce函数

 export default function debounce(func, wait, immediate) {
   var timeout, previous, args, result, context;
 ​
   var later = function() {
     ...
   };
 ​
   var debounced = restArguments(function(_args) {
     ...
   });
 ​
   debounced.cancel = function() {
     ...
   };
 ​
   return debounced;
 }
  • 首先,定义一个创建防抖器的函数,防抖器传入参数

    1. func达到超时时间时的调用函数
    2. wait防抖超时时间
    3. immediate函数调用的时机是在开始还是结束
  • 防抖器包含以下几个属性

    1. timeout定时器
    2. previous前一时间
    3. args超时调用函数的参数
    4. result超时调用函数的返回结果
    5. context函数调用的上下文
    6. later重新计时函数
    7. debounced超时后的函数调用,以及debounced.cancel取消防抖器
  • 最后返回防抖器对象

later函数

重新计时函数

 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 passed = now() - previous;首先计算前后事件触发的时间间隔
  • 判断时间间隔是否小于用户设置的等待时间,是则重新设置定时器的时间为等待时间减去经过的时间(这里和我所了解到的防抖不太相同,我的理解是没到时间触发时会将超时时间重新设置为等待时间,但这里设置为等待时间减去上一次所经过的时间,所以只要有事件触发,间隔wait设置的时间就会执行一次,比起防抖更像是我理解的节流);
  • 判断时间间隔大于或等于用户设置的等待时间,则将定时器置空,立即执行为false时执行对应的函数调用,
  • 当计时器为空时,将参数和上下文也置为空

debounced函数

 var debounced = restArguments(function(_args) {
   context = this;
   args = _args;
   previous = now();
   if (!timeout) {
     timeout = setTimeout(later, wait);
     if (immediate) result = func.apply(context, args);
   }
   return result;
 });

看到这里感觉自己好像对于later函数理解有些偏差,先将这个函数理清楚再重新梳理一下

  • 大致看了下restArguments函数的作用,主要是用来处理函数调用时传递的参数,这里暂时不展开说明(其实是人太菜了,只理解到这么多😂)
  • 获取调用上下文context设置值为this,设置调用参数args,获取当前时间
  • 如果定时器为空,设置定时器,等待wait时间后定时调用later;好的,从这里开始可以看到前面的理解确实出错了,真正设置定时器的是在这里,而later函数中重新设置的定时器只是为了在wait时间后执行func函数,应此将时间设置为了wait - passed,和后一段代码就是在计时开始时执行或是计时结束后执行的区别
  • if (immediate) result = func.apply(context, args);判断是不是立即执行,是的话则在定时器创建之后立即执行函数,并且在wait时间内不会再被触发

cancel函数

取消

 debounced.cancel = function() {
   clearTimeout(timeout);
   timeout = args = context = null;
 };
  • clearTimeout()取消定时器
  • 设置timeoutargscontext为空

3. 原理及实现

通过上面的代码,结合一些文章可以非常容易的理解到防抖的原理,暨通过一个定时器将调用的函数延时,在一段时间内再次触发时会重置定时器以便重新计时;但是原理虽然简单,但在具体的实现过程中还需要考虑更多的细节,这也就是体现一个程序经验的地方了,那对于我这种菜鸟来说如何提升自己的经验呐,最快的就莫过于读源码了,从大佬的代码中学习吸收大佬的经验。

实现

通过对上面代码的理解,自己动手实现一个简单防抖函数

 function debounce(func, wait) {
     var timeout;
     return function () {
         var context = this;
         var args = arguments;
         var result;
         clearTimeout(timeout);
         timeout = setTimeout(function(){
             result = func.apply(context, args);
         }, wait);
         return result;
     }
 }
  • 这里的实现我没有添加立即执行immediate控制条件以及取消cancel函数

4. 总结

通过underscore学习到了防抖的基本原理以及简单实现,之后还可以自己再学习一下节流相关的原理和实现。加油加油!!👍