只取最后一次的有效操作-防抖

1,830 阅读3分钟

「我正在参与掘金会员专属活动-源码共读第一期,点击参与

本篇文章介绍的是debounce防抖。

防抖很常见了,几乎随处可见,例如你在掘金写文章的时候,不停地在左边的编辑栏疯狂的输出,右边的预览栏没有立刻处理,只是在你短暂停顿的那一刻,将你的文字在右边显示出来。因为这里运用了防抖,作用是:防止重复无效的操作,只取最后一次有效操作。

用户交互这块使用防抖也有场景:

  • 用户拖拽浏览器边框
  • 键盘敲击
  • 鼠标移动

源码

这些操作也是很频繁的,没必要每次都处理,我们只处理最后那一次就好了。

简版

function debounce(func, wait = 1000) {
      let timer;
      return function () {
        clearTimeout(timer);
        timer = setTimeout(func, wait);
      };
}

下图是直接调用和debounce调用的this和参数比较

image.png

问题:

  1. this指向错误
  2. 函数参数丢失
  3. 函数返回值取不到

解决this问题

获取到当前this,然后通过apply强制绑定this

function debounce(func, wait = 1000) {
      let timer;
      return function () {
        let context = this
        clearTimeout(timer);
        timer = setTimeout(function (){
            func.apply(context)
        },wait);
      };
}

如下图:现在this不一致的问题解决了 image.png

参数丢失问题

通过arguments这个函数内置对象获取函数的参数

function debounce(func, wait = 1000) {
      let timer;
      return function () {
        let context = this, args = arguments
        clearTimeout(timer);
        timer = setTimeout(function (){
            func.apply(context,args)
        },wait);
      };
}

如下图:现在函数的参数能接收到了

image.png

函数返回值

这个好办在函数直接的时候通过一个变量接收

  function debounce(func, wait = 1000) {
      let timer, result;
      return function () {
        let context = this,
          args = arguments;
        clearTimeout(timer);
        timer = setTimeout(function () {
          result = func.apply(context, args);
          console.log(result);
        }, wait);
      };
   }

立即执行

如果想立即执行,不想等时间到了之后执行,需要加入一个是否立即执行的标志

 function debounce(func, wait = 1000, immediate = false) {
      let timer, result;
      return function () {
        let context = this,
          args = arguments;
        clearTimeout(timer);
        if(immediate){
            var callnow = !timer // 防止重复执行
            timer = setTimeout(()=>{
               timer = null
            },wait) //timer延迟置为null 函数直接执行
            if(callnow) result = func.apply(context, args);
        }else{
           // 否则就延迟执行
            timer = setTimeout(function () {
              result = func.apply(context, args);
            }, wait);
        }
      };
   }

重置debounce

想在延迟的过程中取消抖动,重新开启一个。

首先将返回的函数保存到debounce,然后增加一个cancel属性用于重置变量。

function debounce(func, wait = 1000, immediate = false) {
      let timer, result;
      var debounce = function () {
        let context = this,
          args = arguments;
        clearTimeout(timer);
        if(immediate){
            var callnow = !timer // 防止重复执行
            timer = setTimeout(()=>{
               timer = null
            },wait) //timer延迟置为null 函数直接执行
            if(callnow) result = func.apply(context, args);
        }else{
           // 否则就延迟执行
            timer = setTimeout(function () {
              result = func.apply(context, args);
            }, wait);
        }
      };
      debounce.cancel = function(){
          clearTimeout(timer);
          timer = result = null
      }
      return debounce
   }

结束语

在使用函数作为参数的时候,要首先保证包裹后的函数执行和未包裹前的函数的执行效果是一样的。也就是说在对函数做一些功能加强的时候,不能改变函数原来的逻辑。wrapped(fn)

防止抖动在运用的场景非常广泛,有时候还得注意浏览器的兼容和setTimeout时间不准确的问题。