防抖与节流

132 阅读4分钟

在进行窗口的resizescroll,输入框内容校验等操作时,如果事件处理函数调用的频率无限制,会加重浏览器的负担,导致用户体验非常糟糕。此时我们可以采用debounce(防抖)throttle(节流)的方式来减少调用频率,同时又不影响实际效果。这其实属于前端优化的范畴了。

函数防抖(debounce:当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。简单来说就是公交车停留30秒,只要中间有人上车,司机就不能走,除非最后30秒结束,没人再上车,公交车才可以开走。

const debounce = (fn, wait) => {
    let timeout = null;
    return () => {
        if(timeout) clearTimeout(timeout);
        timeout = setTimeout(fn, wait);
    }
}
// 处理函数
const handle = () => {
    console.log(Math.random()); 
}
// 滚动事件
window.addEventListener('scroll', debounce(handle, 1000));

函数节流(throttle:当持续触发事件时,保证一定时间段内只调用一次事件处理函数。节流通俗解释就比如我们水龙头放水,阀门一打开,水哗哗的往下流,秉着勤俭节约的优良传统美德,我们要把水龙头关小点,最好是如我们心意按照一定规律在某个时间间隔内一滴一滴的往下滴。

函数节流主要有两种实现方法:时间戳和定时器。接下来分别用两种方法实现throttle

节流throttle代码(时间戳):

const throttle = (func, delay) => {
    let prev = Date.now();
    return () => {
          const context = this;
          const args = arguments;
          const now = Date.now();
          if (now - prev >= delay) {
             func.apply(context, args);
             prev = Date.now();
          }
    }
}
// 处理函数
const handle = () => {
     console.log(Math.random());
}
// 滚动时间
window.addEventListener('scroll', throttle(handle, 1000));

当高频事件触发时,第一次会立即执行,而后再怎么频繁地触发事件,也都是每delay时间才执行一次。而当最后一次事件触发完毕后,事件也不会再被执行了。

节流throttle代码(定时器):

const throttle = (func, delay) => {
    let timer = null;
    return () => {
        const context = this;
        const args = arguments;
        if (!timer) {
           timer = setTimeout(() => {
                   func.apply(context, args);
                   timer = null;
           }, delay);
        }
    }
}
// 处理函数
const handle = () => {
    console.log(Math.random());
}
// 滚动事件
window.addEventListener('scroll', throttle(handle, 1000));

当触发事件的时候,我们设置一个定时器,再次触发事件的时候,如果定时器存在,就不执行,直到delay时间后,定时器执行执行函数,并且清空定时器,这样就可以设置下个定时器。当第一次触发事件时,不会立即执行函数,而是在delay秒后才执行。而后再怎么频繁触发事件,也都是每delay时间才执行一次。当最后一次停止触发后,由于定时器的delay延迟,可能还会执行一次函数。

节流中用时间戳或定时器都是可以的。更精确地,可以用时间戳+定时器,当第一次触发事件时马上执行事件处理函数,最后一次触发事件后也还会执行一次事件处理函数。

节流throttle代码(时间戳+定时器):

const throttle = (func, delay) => {
     let timer = null;
     let startTime = Date.now();
     return () => {
           const curTime = Date.now();
           const remaining = delay - (curTime - startTime);
           const context = this;
           const args = arguments;
           clearTimeout(timer);
           if (remaining <= 0) {
              func.apply(context, args);
              startTime = Date.now();
           } else {
              timer = setTimeout(func, remaining);
           }
      }
}
// 处理函数
const handle = () => {
      console.log(Math.random());
}
// 滚动事件
 window.addEventListener('scroll', throttle(handle, 1000));

在节流函数内部使用开始时间startTime、当前时间curTimedelay来计算剩余时间remaining,当remaining <= 0时表示该执行事件处理函数了(保证了第一次触发事件就能立即执行事件处理函数和每隔delay时间执行一次事件处理函数)。如果还没到时间的话就设定在remaining时间后再触发(保证了最后一次触发事件后还能再执行一次事件处理函数)。当然在remaining这段时间中如果又一次触发事件,那么会取消当前的计时器,并重新计算一个remaining来判断当前状态。

总结

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

函数节流:使得一定时间内只触发一次函数。原理是通过判断是否到达一定时间来触发函数。

区别: 函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数。 比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次Ajax请求,而不是在用户停下滚动页面操作时才去请求数据。这样的场景,就适合用节流技术来实现。