节流、防抖探究

227 阅读3分钟

以下场景往往由于事件频繁被触发,因而频繁执行DOM操作、资源加载等重行为,导致UI停顿甚至浏览器崩溃。

  1. window对象的resize、scroll事件

  2. 拖拽时的mousemove事件

  3. 文字输入、自动完成的keyup事件

  4. 用户快速click事件

针对这样的需求就出现了debouncethrottle两种解决办法。

比如:实现一个原生的拖拽功能(不能用 H5 Drag&Drop API),需要一路监听 mousemove 事件,在回调中获取元素当前位置,然后重置 dom 的位置。如果我们不加以控制,每移动一定像素而触发的回调数量是会非常惊人的,回调中又伴随着 DOM 操作,继而引发浏览器的重排与重绘,性能差的浏览器可能就会直接假死,这样的用户体验是非常糟糕的。我们需要做的是降低触发回调的频率,比如让它 500ms 触发一次,或者 200ms,甚至 100ms,这样的解决方案就是函数节流


节流函数节流的核心是,让一个函数不要执行得太频繁,减少一些过快的调用。

在这里插入图片描述

// 代码片段
const throttle = (fn, timeGap) => {
  var last;
   // 定时器
   var timer;
   return function () {
       // 保存函数调用时的上下文和参数,传递给 fn
       var context = this;
       var args = arguments;
       // console.log(args, 'args');
       var now = +new Date();
       // 执行 fn,并重新计时
       if (last && now < last + timeGap) {
           clearTimeout(timer);
           // 保证在当前时间区间结束后,再执行一次 fn
           timer = setTimeout(function () {
               last = now;
               fn.apply(context, args);
           }, timeGap)
           // 在时间区间的最开始和到达指定间隔的时候执行一次 fn
       } else {
           last = now;
           fn.apply(context, args);
       }
   };
};

对于浏览器窗口,每做一次 resize 操作,发送一个请求,就需要监听 resize 事件,但是和 mousemove 一样,每缩小(或者放大)一次浏览器,实际上会触发 N 多次的 resize 事件,用节流的话,节流只能保证定时触发,我们一次就好,这就要用去抖。 简单的说,函数去抖就是对于一定时间段的连续的函数调用,只让其执行一次。

https://images2018.cnblogs.com/blog/1022151/201806/1022151-20180613144209623-862434090.jpg

// 代码片段
const debounce = (fn, time) => {
   let timer = null;
   return function () {
       // console.log('arguments', args);
       clearTimeout(timer);
       const args = arguments;
       const context = this;
       timer = setTimeout(() => {
           fn && fn.apply(context, args);
       }, time);
   };
};

以下是自己在实际项目中遇到的问题:

描述:当单击头像显示个人资料页,双击没有任何反应。

在这里插入图片描述
这是上述图片的clickFun函数

var last;
var timer = null;
function clickFun(e) {
  const now = new Date().getTime();
  if (last) {
      clearTimeout(timer);
      timer = null;
      if (now - last > 400) {
          timer = setTimeout(() => {
              clickThrottle(e);
          }, 400);
      }
      last = now;
  }
  else {
      timer = setTimeout(() => {
          clickThrottle(e);
      }, 400);
      last = now;
  }
}

如果使用去抖的话,在指定时间内可能会多次执行的只会被打包成一次。那如果用户过一段时间再次进行单击/双击操作的话,双击其实还是会进行操作,并不能达到想要的效果,所以这里才用了节流跟定时器的结合(当用户进行点击的时候,在*秒内只能触发一次,这样就可以规避双击的问题)。

总结:

节流throttle :使得一定时间内只触发一次函数。 它和防抖动最大的区别就是,节流函数不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而防抖动只是在最后一次事件后才触发一次函数。 原理是通过判断是否到达一定时间来触发函数,若没到规定时间则使用计时器延后,而下一次事件则会重新设定计时器。

防抖动debounce:将几次操作合并为一此操作进行。原理是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。