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

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


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


今天带来的是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的风格,简洁,高效。


目录
相关文章
|
6月前
|
前端开发 JavaScript Java
面试官:什么是防抖和节流?如何实现?应用场景?
面试官:什么是防抖和节流?如何实现?应用场景?
108 0
|
3月前
|
前端开发 UED 开发者
颠覆你的前端知识:防抖与节流的区别及实战解析!
【8月更文挑战第23天】在Web前端开发中,处理用户界面交互产生的事件可能会影响性能。为此,我们有两种优化方法:防抖(debounce)和节流(throttle)。防抖确保函数仅在事件停止触发一段时间后执行一次,适用于如搜索自动补全场景。而节流则确保函数按固定时间间隔执行,不管用户操作频率如何。本篇技术博客将深入解析两者差异并提供示例代码,帮助开发者更好地理解和应用这些技巧以提升应用性能和用户体验。
76 0
|
6月前
|
JavaScript 前端开发
【JavaScript】面试手撕节流
上篇我们讲了防抖,这篇我们就谈谈防抖的好兄弟 -- 节流。这里在老生常谈般的提一下他们两者之间的区别,顺带给读者巩固下。
75 3
|
6月前
|
前端开发 JavaScript 程序员
如何实现一个让面试官拍大腿的防抖节流函数
如何实现一个让面试官拍大腿的防抖节流函数
|
6月前
|
人工智能 前端开发 Cloud Native
你能手撕节流防抖吗?
你能手撕节流防抖吗?
|
6月前
|
前端开发
【前端学习】—函数节流(九)
【前端学习】—函数节流(九)
|
12月前
|
C++
《C++避坑神器·二十一》回调函数使用
《C++避坑神器·二十一》回调函数使用
92 0
|
存储 JavaScript 前端开发
纯手硬撸Redux
当今不管作为一个前端小白还是一个资深的前端攻城狮。如果不掌握几种前端框架(React,Vue,ng),都不好意思出去说自己是做前端。但是面对如此之多的前端框架,尤其是React、Vue这种纯负责UI展示的架子来说。有一件事是绕不开的就是前端的数据存储问题。 作为业界层出不穷的数据处理框架Redux(React的数据存储框架)又是不得不提起的。 Vue的数据处理一般用Vuex。但是他的设计思路都是基于Redux等。 所以,有必要看看Redux是如何实现数据存储,又如何使得存储的数据被组件获取,并且组件在触发CRUD的时候,能够及时更新数据呢。 我们就按照Redux的实现原理来剖析一下这些数据存储
纯手硬撸Redux
|
JavaScript
【连载】手摸手解析JS手写面试题题系列2——实现throttle(节流)方法
【连载】手摸手解析JS手写面试题题系列2——实现throttle(节流)方法
116 0
|
前端开发
✨性能优化之防抖节流篇
✨性能优化之防抖节流篇
103 2
✨性能优化之防抖节流篇