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

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


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


今天带来的是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);

image.png

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


第一次执行是立即执行;


第二次执行是在等了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的风格,简洁,高效。


目录
相关文章
|
5天前
|
前端开发 JavaScript Java
面试官:什么是防抖和节流?如何实现?应用场景?
面试官:什么是防抖和节流?如何实现?应用场景?
|
5天前
|
前端开发 JavaScript 程序员
如何实现一个让面试官拍大腿的防抖节流函数
如何实现一个让面试官拍大腿的防抖节流函数
|
5天前
|
前端开发 JavaScript 程序员
【面试题】 js-面试官要求手写节流防抖?
【面试题】 js-面试官要求手写节流防抖?
|
5天前
|
前端开发
【前端学习】—函数节流(九)
【前端学习】—函数节流(九)
|
8月前
|
监控 前端开发
前端经典面试题 | 理解 节流 和 防抖(后附手写节流\防抖)
前端经典面试题 | 理解 节流 和 防抖(后附手写节流\防抖)
|
9月前
|
前端开发
手写节流
手写节流
48 1
|
10月前
|
缓存 移动开发 JavaScript
vue面试提整理偏原理
vue面试提整理偏原理
48 0
|
11月前
|
缓存 边缘计算 JavaScript
web前端面试高频考点——性能优化篇(手写防抖、手写节流、XXS攻击、XSRF攻击)
web前端面试高频考点——性能优化篇(手写防抖、手写节流、XXS攻击、XSRF攻击)
121 0
|
12月前
|
JavaScript
【连载】手摸手解析JS手写面试题题系列2——实现throttle(节流)方法
【连载】手摸手解析JS手写面试题题系列2——实现throttle(节流)方法
|
前端开发
步步为营,手写promise
步步为营,手写promise
71 0