前端实现防抖和节流(简版&完备)

一、防抖

防抖函数是一种常用的优化方法,可以避免在短时间内频繁触发某个函数而导致性能问题。

作用是在一定时间内,如果重复触发同一个函数,只执行最后一次,以减少函数执行次数,节省性能。

防抖简易版:

以下是一个简单的防抖函数实现:

function debounce(func, delay) {
  let timer;
  return function() {
    const context = this;
    const args = arguments;
    clearTimeout(timer);
    timer = setTimeout(() => {
      func.apply(context, args);
    }, delay);
  }
}

这个防抖函数接受两个参数,第一个参数是要执行的函数,第二个参数是延迟时间。函数内部创建了一个计时器,每次函数被调用时,先清除之前的计时器,并重新设置一个新的计时器,在延迟时间后执行函数。如果在延迟时间内再次调用函数,会清除之前的计时器,并重新设置一个新的计时器,直到延迟时间内没有新的调用,才会执行函数。

防抖完备版:

下面是一个使用JavaScript实现的功能完备的防抖函数:

function debounce(func, wait, immediate) {
  let timeout, args, context, timestamp, result;

  const later = function() {
    const last = Date.now() - timestamp;

    if (last < wait && last >= 0) {
      timeout = setTimeout(later, wait - last);
    } else {
      timeout = null; 
      if (!immediate) {
        result = func.apply(context, args);
        if (!timeout) context = args = null;
      }
    }
  };

  return function() {
    context = this;
    args = arguments;
    timestamp = Date.now();
    const callNow = immediate && !timeout;
    if (!timeout) timeout = setTimeout(later, wait);
    if (callNow) {
      result = func.apply(context, args);
      context = args = null;
    }

    return result;
  };
}

这个防抖函数接收三个参数:

  • func:要防抖的函数
  • wait:等待时间,单位为毫秒
  • immediate:是否立即执行

如果 immediate 设置为 true,则函数会在第一次触发时立即执行,不会等待 wait 时间。如果设置为 false 或不传入该参数,则函数会在最后一次触发后等待 wait 时间再执行。

在函数内部,使用闭包保存了 timeoutargscontexttimestampresult 这些变量。在每次函数被触发时,都会更新这些变量的值,并根据 immediatetimeout 的状态来决定是否调用 func。如果 timeout 存在,则表示函数已经被触发过了,需要等待 wait 时间再执行;如果不存在,则表示函数尚未被触发,需要创建一个新的 timeout 并设置等待时间;如果 immediatetrue,还需要在第一次触发时立即执行。

使用这个防抖函数非常简单,只需要把要防抖的函数和等待时间传入即可。例如:

const debouncedFn = debounce(myFunction, 500);

这样就创建了一个防抖的函数 debouncedFn,它会在 myFunction 被触发后等待 500 毫秒再执行。如果需要立即执行,可以设置 immediate: true

const debouncedFn = debounce(myFunction, 500, true);

这样 myFunction 就会在第一次触发时立即执行,然后在接下来的 500 毫秒内不会再次执行,直到最后一次触发后再执行一次。

二、节流

节流简易版:

function throttle(func, wait) {
  let lastTime = 0;
  return function() {
    const now = Date.now();
    if (now - lastTime >= wait) {
      func.apply(this, arguments);
      lastTime = now;
    }
  }
}

节流完备版:

function throttle(fn, delay) {
  let lastCallTime timeoutId;

  return function(...args) {
    const now = Date.now();

    if (lastCallTime && now < lastCallTime + delay) {
      clearTimeout(timeoutId);

      timeoutId = setTimeout(() => {
        lastCallTime = now;
        fn.apply(this, args);
      }, delay);
    } else {
      lastCallTime = now;
      fn.apply(this, args);
    }
  }
}

使用示例:

function handleScroll() {
  console.log('scroll event handled');
}

const throttledHandleScroll = throttle(handleScroll, 1000);

window.addEventListener('scroll', throttledHandleScroll);

上述代码中,throttle 函数接收两个参数:要节流的函数 fn 和节流延迟时间 delay,并返回一个新的函数。

每当新的 fn 被调用时,throttle 函数会记录当前时间 now。如果上一次调用 fn 的时间和 now 的时间差小于 delay,则会清除上一个定时器并再次设置一个新的定时器,以延迟调用 fn 函数。否则,直接调用 fn 函数。

这样做可以确保 fn 函数最多每隔 delay 毫秒被调用一次,以避免在高频事件中频繁执行代码导致的性能问题。

posted @ 2023-03-16 18:21  MartinL  阅读(226)  评论(0编辑  收藏  举报