JavaScript:防抖与节流(js + react hook 版)

164 阅读4分钟

当在前端开发中处理事件时,防抖和节流是两个常用的技术,用于限制事件的触发频率,提高性能和用户体验。

防抖 (Debounce): 当事件被触发时,防抖技术会在一定的时间内(比如300毫秒)等待更多的事件,只在最后一次事件触发后才执行相应的操作。如果在这段时间内又触发了该事件,那么重新计时。这样的效果可以避免频繁触发事件导致的性能问题。

应用场景举例:

  1. 用户输入搜索框:比如百度搜索,输入内容时需要根据内容加载候选列表,但又不能每输入一个字符就要获取数据,这样过度频繁获取数据会造成一些性能问题及资源浪费等等,因此要在输入过程中有稍微停顿时再获取数据;
  2. 滚动条事件监听:有时候需要监听滚动条拉至指定位置时去做一些事,比如下拉至底部时要加载下一批数据,如果不做合适的控制,当滚轮不断上下滚动,会多次触发加载数据的行为,同样会造成一些性能问题及资源浪费等等,因此需要防抖来解决这些问题。

节流 (Throttle): 节流技术与防抖技术类似,但它是在固定的时间间隔(比如300毫秒)内执行事件的操作,而不是等待最后一次事件触发。如果在这段时间内触发了多次事件,只有在时间间隔过去后才会执行一次操作。这样的效果可以减少事件触发的频率,特别适用于需要控制频繁操作的情况。

应用场景举例:

  1. 防止按钮重复点击:当用户点击按钮时,可能会因为网络延迟或用户误操作导致多次触发相同的操作。为了避免重复执行,可以使用节流技术,在一定时间内只允许执行一次按钮点击操作。
  2. 窗口调整事件:当窗口大小调整时,浏览器会触发resize事件。如果在调整窗口大小的过程中频繁执行某些操作,可能会导致浏览器性能下降。使用节流函数可以降低操作的执行频率。

以上举例只是针对一些实际的应用场景,像窗口调整事件也有用 防抖 (Debounce) 的场景,主要取决于你的实际需求,比如窗口大小变化后,只想执行一次操作,可以用防抖,如果想在窗口变化过程中做一些事(比如做些什么计算之类的),可以用节流

防抖示例代码

/** 防抖
 * @param {function} func 需要防抖的执行函数
 * @param {number} delay 函数执行延时时长(ms)
 */
function debounce(func, delay) {
    var timer;

    return function (...args) {
        clearTimeout(timer);

        timer = setTimeout(function () {
            func.apply(this, args);
        }, delay);
    };
};

实测效果:

// 使用
const handleInput = debounce(() => {
    console.log("防抖演示:一秒内不断触发,稍作停顿后只执行最后一次触发的事件");
}, 1000);

<input placeholder="debounce" onInput={handleInput} />

2.gif

节流示例代码

/** 节流
 * @param {function} func 需要节流的执行函数
 * @param {number} delay 函数执行频率(时间间隔:ms)
 */
function throttle(func, delay) {
    var lastExecutionTime = 0;

    return function (...args) {
        var currentTime = Date.now();

        if (currentTime - lastExecutionTime >= delay) {
            func.apply(this, args);
            lastExecutionTime = currentTime;
        }
    };
};

或者使用 setTimeout:

/** 节流
 * @param {function} func 需要节流的执行函数
 * @param {number} delay 函数执行频率(时间间隔:ms)
 */
function throttle(func, delay) {
  var timer;

  return function (...args) {
    if (!timer) {
      timer = setTimeout(function () {
        func.apply(this, args);
        timer = null;
      }, delay);
    }
  };
}

实测效果:

// 使用
const handleClick = throttle(() => {
  console.log("节流演示:不管点击多快,只会每隔一秒执行一次");
}, 1000);

<button onClick={handleClick}>throttle</button>

1.gif

React Hook 版

防抖Hook:

// useDebounce.js
import { useRef, useEffect, useCallback } from 'react';
/** 防抖
 * @param {function} func 需要防抖的执行函数
 * @param {number} delay 函数执行延时时长(ms)
 * @param {Array<any>} deps 自定义hook依赖
 */
export function useDebounce(func, delay, deps = []) {
    const { current } = useRef({ func: () => { }, timer: null });

    useEffect(function () {
        current.func = func;
    }, [func]);

    return useCallback(function (...args) {
        if (current.timer) {
            clearTimeout(current.timer);
        }

        current.timer = setTimeout(function () {
            current.func.call(this, ...args);
        }, delay);
    }, deps);
}

使用示例:

import { useDebounce } from './useDebounce';

function App() {
  const handleInput = useDebounce(() => {
    console.log("防抖演示:一秒内不断触发,稍作停顿后只执行最后一次触发的事件");
  }, 1000);

  return (
    <div>
      <input placeholder="debounce" onInput={handleInput} />
    </div>
  );
}

export default App;

节流Hook:

// useThrottle.js
import { useRef, useEffect, useCallback } from 'react';

/** 节流
 * @param {function} func 需要节流的执行函数
 * @param {number} delay 函数执行频率(时间间隔:ms)
 * @param {Array<any>} deps 自定义hook依赖
 */
export function useThrottle(func, delay, deps = []) {
    const { current } = useRef({ func: () => { }, timer: null });

    useEffect(function () {
        current.func = func;
    }, [func]);

    return useCallback(function (...args) {
        if (!current.timer) {
            current.timer = setTimeout(function () {
                current.timer = null;
            }, delay);

            current.func.call(this, ...args);
        }
    }, deps);
}

使用示例:

import { useThrottle } from './useThrottle';

function App() {
  const handleClick = useThrottle(() => {
    console.log("节流演示:不管点击多快,只会每隔一秒执行一次");
  }, 1000);

  return (
    <div>
      <button onClick={handleClick}>throttle</button>
    </div>
  );
}

export default App;

以上代码只进行了简单的封装,提供大概的思路,可根据各自需要进行扩展(比如可暂停,取消防抖/节流等功能)