防抖和节流 - panchaow/blog GitHub Wiki

防抖和节流

防抖 debounce

假设我们要实现一个用户注册的功能,其中有一项用户名需要保证唯一。因此,我们需要在用户输入用户名之后,发起一次请求,确认用户名是否已经被占用。

于是我们想到只要监听<input>的input事件,在这个事件触发的时候,发起请求。代码如下:

<input id="username" name="username" type="text">

<script>
  const username = document.getElementById("username");

  username.addEventListener("input", function (e) {
    const value = e.target.value;
    setTimeout(() => {
      console.log(value);
    }, 1000);
  })
</script>

但是这样的实现存在着一个问题,事实上,我们只关心用户最后输入的值是否被占用,而不关心输入过程中的中间状态。比如用户连续输入了"orange",我们没必要在用户只输入了"ora"的时候就发起一次请求。那么该如何解决这个问题呢?我们只需要在用户输入用户名,并在一段时间内(比如100ms)没有继续输入时发起一次请求。利用防抖(debounce)高阶函数,我们就可以实现这种效果。

debounce的实现:

function debounce(fn, wait = 0) {
  let timer;

  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, args);
      timer = null;
    }, wait);
  }
}

使用防抖后的代码:

<input id="username" name="username" type="text">

<script>
  const username = document.getElementById("username");

  username.addEventListener("input", debounce(function (e) {
    const value = e.target.value;
    setTimeout(() => {
      console.log(value);
    }, 1000);
  }, 100));
</script>

这样,就只会在用户输入后,并且在100ms内没有继续输入的情况下,发起一次请求。

节流 throttle

假设我们需要实现用户移动鼠标时,根据鼠标的坐标进行某些计算的功能。代码如下:

document.addEventListener("mousemove", function(e) {
  const { clientX, clientY } = e;
  
  console.log(clientX, clientY);
});

这段代码存在的问题是如果要进行的计算比较昂贵,那么如此频繁的计算将可能会造成页面卡顿,影响用户体验。假如我们能够限制昂贵的计算在一段时间内(比如100ms)只执行一次,那么问题就可以被解决了。throttle的实现如下:

function throttle(fn, wait) {
  let inThrottle, lastTimer;

  return function(...args) {
    if (!lastTime) {
      // 第一次调用
      fn.apply(this, args);
      lastTime = Date.now();
    } else {
      clearTimeout(lastTimer);
      lastTimer = setTimeout(() => {
        fn.apply(this, args);
        lastTime = Date.now();
      }, Math.max(wait - (Date.now() - lastTime), 0)); // 如果过了时间,那么立刻执行(但是还是异步的)
    }
  };
}

使用tottle的例子:

document.addEventListener("mousemove", throttle(function(e) {
  const { clientX, clientY } = e;

  console.log(clientX, clientY);
}, 100));

这样的效果是,第一次调用函数时会立刻执行,之后的100ms内的除最后一次外的调用会被忽略,而最后一次调用会被延迟到距上一次执行100ms之后执行,然后重新开始计时。如果一次调用发生在距离上一次执行100ms后,那么立刻执行一次,并重新开始计时。

⚠️ **GitHub.com Fallback** ⚠️