函数式防抖和节流

194 阅读2分钟

3. 工作中用过防抖和节流吗?

3.1 基本概念:

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

  • 防抖:当持续触发一个事件时,一定时间内没有再触发事件时,事件处理函数才会执行,一直向后顺延;
  • 节流:当持续触发一个事件时,保证一定时间段内只调用一次事件处理函数;

3.2 分别适合用在什么场景:

  • 节流:input、button、scroll
  • 防抖:input、resize、button

3.3 手写一个防抖函数:

function debounce(fn, delay) {
  let timer = null;
  return function () {
    let context = this;
    let args = arguments;
    if (timer !== null) {
      clearTimeout(timer);
    }
    timer = setTimeout(() => {
        fn.apply(context, args);
      }, delay);
  }
}

3.4 手写一个节流函数:

// 使用时间戳
// 当高频事件触发时,第一次会立即执行,而后再怎么频繁地触发事件,也都是每interval时间才执行一次。
// 而当最后一次事件触发完毕后,事件可能不会再被执行了
function throttle(fn, interval) {
  let last = Date.now();
  return function () {
    let now = Date.now();
    if (now - last >= interval) {
      last = now;
      fn.apply(this, arguments);
    }
  }
}

// 使用定时器
// 当第一次触发事件时,不会立即执行函数,而是在interval秒后才执行。而后再怎么频繁触发事件,也都是每interval时间才执行一次。当最后一次停止触发后,由于定时器的delay延迟,可能还会执行一次函数。
function throttle(fn, interval) {
  let timer = null;
  return function () {
    let context = this;
  	let args = arguments;
    if (timer === null) {
      timer = setTimeout(() => {
        fn.apply(context, args);
        timer = null;
      }, interval);
    }
  }
}

// 结合上述两者,当第一次触发事件时马上执行事件处理函数,最后一次触发事件后也还会执行一次事件处理函数。
function throttle(fn, interval) {
  let timer = null;
  let startTime = Date.now();
  return function () {
    let currentTime = Date.now();
    let remainTime = interval - (currentTime - startTime);
    let context = this;
    let args = arguments;
    if (remainTime <= 0) {
      fn.apply(context, args);
      startTime = Date.now();
    } else {
      timer = setTimeout(fn, remainTime);
    }
  }
}

// 感觉更好的方法:
function throttle (fn, interval) {
  let timer = null;
  return function () {
    if (timer === null) {
      fn.apply(this, arguments);
      
      timer = setTimeout(() => {
        timer = null;
      }, interval);
    }
  }
}