【若川视野 x 源码共读】第25期 | 防抖

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第13天,点击查看活动详情

参考

www.yuque.com/ruochuan12/…

使用场所

事件类型 事件方法名
window上的事件 scroll,resize 等
鼠标事件 mousedown、mousemove、mouseover 等
键盘事件 keyup、keydown

有时候一个按键容易按多次才响应,如果没做loading效果的话,会触发多次。

定义

在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时 防抖动是将多次执行变为最后一次执行

生动的例子:公交车司机一分钟内没有其他人上车,就执行关门开车动作。

具体的例子:百度联想搜索框,每次输入关键字,一定时间间隔后会提示出相关的关键字

基础版本

function debounce(func,wait) {
	let timeout
	return function() {
		//if(timeout){clearTimeout(timeout)} 反正不会报错
        clearTimeout(timeout)
        //setTimeout会把this绑定到全局对象,需要提前绑一下。
        //argument传参数。按理说只有一个参数,func.call(this,e)也不错
		timeout = setTimeout(()=>func.apply(this,argument),wait)
	}
}

function handleScroll(e) {
	console.log('事件对象', e)
	console.log('this',this)
	console.log('滚动了')
}
container.addEventListener('scroll', debounce(handleScroll,1000))
复制代码

立即执行版本

有点do..while的感觉

之前我以为clearTimeout之后timeout就是空了,现在测了一些,是有值的,一串莫名其妙的数字。

 function debounce(func, wait, immediate) {
      let timeout;
      return function () {
        const context = this;
        clearTimeout(timeout);
        //立即执行就是事件刚开始就执行一次先。如每次滚动先执行一次,滚动结束一次。
        //用timeout来判断
        if (immediate) {
          var callNow = !timeout;
          timeout = setTimeout(() => {
              //如果只是想只立即执行一次,就不用 timeout = null; 了,加了每次滚动都会执行一次,不加就只是最最开始的滚动执行一次,后面和平时一样。
            timeout = null;
            func.apply(context, arguments);
          }, wait);
          if (callNow) {
            func.apply(context, arguments);
          }
        } else {
          timeout = setTimeout(() => func.apply(context, arguments), wait);
        }
      };
    }
container.addEventListener('scroll', debounce(handleScroll, 1000,true))
复制代码

返回值

拿到的返回值都是上次定时任务的返回值,初始的返回值都是undefined,这个使用场景应该比较苛刻吧

 var container = document.getElementsByClassName("container")[0];
    function handleScroll(e) {
      console.log("事件对象", e);
      console.log("this", this);
      console.log("滚动了");
      return 1;
    }
    container.addEventListener("scroll", debounce(handleScroll, 1000, false));
    // container.addEventListener("scroll", debounce(handleScroll, 1000, true));
    function debounce(func, wait, immediate) {
      let timeout, res;
      return function () {
        const context = this;
        clearTimeout(timeout);
        //立即执行就是刚开始就执行一次先。后面的按规矩来
        //用timeout来判断
        if (immediate) {
          var callNow = !timeout;
          timeout = setTimeout(() => {
            timeout = null;
            func.apply(context, arguments);
          }, wait);
          if (callNow) {
            func.apply(context, arguments);
          }
        } else {
          timeout = setTimeout(() => {
            res = func.apply(context, arguments);
          }, wait);
        }
        console.log({ res });
        return res;
      };
    }
复制代码

取消防抖

function debounce(func,wait) {
	let timeout
	const debounced = function() {
        clearTimeout(timeout)
		timeout = setTimeout(()=>func.apply(this,argument),wait)
	}
    debounced.cancel=()=>{
        clearTimeout(timeout)
    }
    return debounced
}
复制代码

underscore的debounce

为了理解这个例子,我举了个例子

设wait为1s

开始触发一次,0.4s触发一次,那么应该1.4s执行任务

在debounced里,只会在最开始执行一次later,每次触发都会更新previous

在later里进行比较,如果wait大于now-previous(两次触发间隔),也就是还达不到wait的时间,那需要重新计时,达到了就直接执行。

初始化时later会在1s后触发判断,由于0.4s触发更新了previous,导致两次触发间隔达不到wait,需要延时,以0.4s为基准,按理是1.4s后再判断,此时是时间1s,所以要在0.4s后判断,1s-(当前-之前=0.6)=0.4.

差不多是这样。

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;
}
复制代码

猜你喜欢

转载自juejin.im/post/7107570141893754911