在JS的事件处理中,防抖与节流是一种很重要的处理函数执行方式的手段,比如一些会经常触发的事件(点击,下拉等),如果不加以控制,会对性能造成负担,或者出现BUG。

基本原理

通过settimeout的方式,在一定时间间隔内将多次触发变成一次触发,或者限制出发次数。

<body>
  <input type="text">
  <button type="submit" id="input">提交</button>
</body>
<script>
  var btn = document.getElementById('input');
  btn.addEventListener('click', submit, false);//没有使用防抖

  /*需要执行的函数*/  
  function submit(e) {
    console.log(this); //这里我们希望this指向到input
    console.log('发送请求~');
    console.log(e);
  }
</script>

运行结果可想而知:(狂点。。。)

image-20201109231015599

接下来我们一步步的实现防抖

<script>
  var btn = document.getElementById('input');
  //btn.addEventListener('click', submit, false);//没有使用防抖
    btn.addEventListener('click', debounce(submit,2000), false);//使用防抖
  /*需要执行的函数*/  
  function submit(e) {
    console.log(this); //这里我们希望this指向到input
    console.log('发送请求~');
    console.log(e);
  }
  function debounce(fn,delay){
      var time = null;
      return function(){
        if(time){
         clearTimeout(time);//多次点击就会清除上一个定时器,所以始终按照最后一次的点击时间开始执行 
        }
        time = setTimeout(() => {
          fn();
        }, delay);  
      }  
  }  
</script>

运行结果:

image-20201109231605530

这里点击一次两秒后执行,多次点击以最后一次的点击开始计时,但是我们发现,执行函数中输出的e,和this并不是我们想要的结果。

因为我们通过这种包装的方式实现防抖会影响到参数的取值作用域,所以这里我们在进行优化,解决这个问题。

解决this指向和e取值的问题:

<script>
  var btn = document.getElementById('input');
  //btn.addEventListener('click', submit, false);//没有使用防抖
    btn.addEventListener('click', debounce(submit,2000), false);//使用防抖
  /*需要执行的函数*/  
  function submit(e) {
    console.log(this); //这里我们希望this指向到input
    console.log('发送请求~');
    console.log(e);
  }
  function debounce(fn,delay){
      var time = null;
      return function(...args){//这里可能传递的参数不止事件对象e一个,所以使用这种写法保证参数都传递进去
        if(time){
         clearTimeout(time);//多次点击就会清除上一个定时器,所以始终按照最后一次的点击时间开始执行 
        }
        time = setTimeout(() => {//这里使用箭头函数使得内部的this为上级作用域中的this
          fn.apply(this, args);//这里使用apply使得执行函数fn中的this指向当前作用域的this,而当前作用域中的this指向上级作用域中的this,上级作用域中的this又指向触发点击事件的dom元素,同时也将事件参数传递了进去
        }, delay);
      }  
  }  
</script>

结果:

image-20201109234039280

看似完美,但是还有一个问题,我们每次点击,无论多次点击还是只点击一次,都要等延时结束后才能执行,这在加上网络请求不好的情况下,会对用户造成不好的体验的。我们希望用户第一次点击的时候能立即执行。

接下来继续优化:

<script>
  var btn = document.getElementById('input');
  btn.addEventListener('click', debounce(submit, 2000), false);

  function submit(e) {
    console.log(this); //这里我们希望this指向到input
    console.log('发送请求~');
    console.log(e);
  }
  
  function debounce (fn, interval = 500) {
  let timer, firstTime = true;
  return function (...args) {
    if (firstTime) {
      fn.apply(this, args)
      firstTime = false;
    } else {
      if (timer) {
        clearTimeout(timer);
      }
      timer = setTimeout(() => {
        fn.apply(this, args)
      }, interval)
    }

  }
}

</script>

另外一种限制方法是节流了,连续多次点击,事件会限制次数的一直触发 的效果,和防抖有点区别,写法简单,主要利用了时间戳的方法进行限制。

<script>
  var btn = document.getElementById('input');
  btn.addEventListener('click', throttle(submit, 2000), false)

  function submit(e) {
    console.log(this); //这里我们希望this指向到input
    console.log('发送请求~');
    console.log(e);
  }
  function throttle(fn,delay){
    var begin = 0;
    return function(...args){
      var cur = new Date().getTime();//获取当前的时间戳
      if(cur - begin > delay){//点击(连续)触发事件大于了
        fn.apply(this,args);
        begin = cur;
      }
    }
  }
</script>

当用户以很快的速度不停点击时,函数总是以规定的延时 间隔进行执行。这样就实现了节流效果

Last modification:April 12, 2022
如果觉得我的文章对你有用,请随意赞赏