likes
comments
collection
share

【源码共读】防抖的原理和实现

作者站长头像
站长
· 阅读数 56

防抖指的是在一定时间内,函数只能执行一次,如果在这个时间内再次触发,则重新计算时间,直到时间到了,才执行函数,这样就可以避免频繁触发函数,造成性能浪费。

今天带来的是underscore中的防抖函数,loadash中也有,但是loadash中的防抖函数阅读起来会有比较大的心智负担。

源码地址:

防抖的实现

underscoredebounce的实现比较简单,核心代码如下:

import restArguments from './restArguments.js';
import now from './now.js';

// When a sequence of calls of the returned function ends, the argument
// function is triggered. The end of a sequence is defined by the `wait`
// parameter. If `immediate` is passed, the argument function will be
// triggered at the beginning of the sequence instead of at the end.
export default function debounce(func, wait, immediate) {
  var timeout, previous, args, result, context;

  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 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;
  });

  debounced.cancel = function() {
    clearTimeout(timeout);
    timeout = args = context = null;
  };

  return debounced;
}

上面的两个引用就不看了,我简单的介绍一下他们的作用:

  • restArguments:看名字大概就知道,是用来处理剩余参数的,因为可能有些函数的参数是不确定的,所以需要用这个来处理一下。
  • now:获取当前时间,用来计算时间差。

源码解析

当我们在使用debounce的时候,会传入三个参数,func是我们需要防抖的函数,wait是防抖的时间,immediate是是否立即执行。

var debounced = debounce(function() {
  console.log('debounce');
}, 500, true);

【源码共读】防抖的原理和实现

可以看到上面的执行截图,我们执行了很多次,但是最终只执行了两次;

第一次执行是立即执行;

第二次执行是在等了500ms之后再次调用,然后立即执行的。

返回一个函数

防抖函数返回的就是一个函数,我们将去执行返回的函数,这个就是通过闭包实现的;

underscore中的debounce函数使用了restArguments来处理剩余参数,这个函数也是返回一个函数,我们分析的时候可以忽略这个函数:

function debounce(func, wait, immediate) {
  var timeout, previous, args, result, context;
  
  var debounced = function() {
      context = this;
      args = [].slice.call(arguments);
      previous = Date.now();
      if (!timeout) {
          timeout = setTimeout(later, wait);
          if (immediate) result = func.apply(context, args);
      }
      return result;
  }

  return debounced;
}

通过简化代码,我们现在可以很直观的看到返回的函数内容;

这里使用[].slice.call(arguments)来处理参数的问题,当前时间使用Date.now()来获取。

定时器

防抖的核心其实并不是返回的函数,而是定时器,我们来看看定时器的实现;

在上面的代码中可以看到,当我们执行返回的函数的时候,会先判断是否有定时器,如果没有定时器是不会执行对应的函数的;

来看看定时器的实现:

var later = function () {
    var passed = Date.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;
    }
};

previous是上一次执行的时间,wait是防抖的时间,passed是当前时间和上一次执行的时间差;

如果wait大于passed,说明还没有到防抖的时间,所以会重新设置定时器,这样就可以保证在防抖的时间内,只执行一次;

如果wait小于passed,说明已经到了防抖的时间,这时候就会执行对应的函数,如果是立即执行的话,就说明执行过了, 就不需要再执行了,后面就是一些释放内存的操作。

cancel

最后还有一个cancel方法,这个方法是用来取消防抖的,给我们提供了一个手动取消的方法;

debounced.cancel = function () {
    clearTimeout(timeout);
    timeout = args = context = null;
};

cancel方法是直接挂在返回的函数上的,所以我们可以在外部手动调用这个方法来取消防抖。

总结

防抖函数的实现其实并不复杂,核心就是定时器,我们可以通过定时器来控制函数的执行,从而达到防抖的效果。

同时,underscore中的防抖函数还提供了一个cancel方法,这样我们可以在外部手动取消防抖,例如组件销毁的时候,这样就可以进一步的优化性能,虽然有点微乎其微,但是有这样的一个设计可以说是考虑的非常周到了。

对比于lodash的防抖函数,underscore的防抖函数更加的简洁,这也是underscore的风格,简洁,高效。

转载自:https://juejin.cn/post/7178872220608823354
评论
请登录