探索性能优化——防抖和节流

370 阅读6分钟

在现代前端开发中,性能优化是一个永恒的话题。用户体验的好坏往往取决于页面的响应速度和流畅度。而在这些优化技巧中,有两种技术尤为重要,那就是防抖(Debounce)节流(Throttle)

什么是防抖和节流?

浏览器的 resizescrollkeypressmousemove 等事件在触发时,会不断地调用绑定在事件上的回调函数,极大地浪费资源,降低前端性能

为了优化体验,需要对这类事件进行调用次数的限制,对此我们就可以采用 防抖(debounce)  和 节流(throttle)  的方式来减少调用频率

定义

  • 节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效
  • 防抖: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时
防抖的实现

让我们通过一个简单的例子来理解防抖的实现。假设我们有一个函数 search,它会在用户输入时触发请求:

function debounce(func, wait) {
    let timeout;
    return function(...args) {
        clearTimeout(timeout);
        timeout = setTimeout(() => func.apply(this, args), wait);
    };
}

const search = debounce(() => {
    console.log('Sending request');
}, 300);

document.getElementById('searchInput').addEventListener('input', search);

在上面的代码中,debounce 函数接收两个参数:需要防抖的函数 func 和延迟时间 wait。每次用户输入时,都会清除之前的计时器并重新设置一个新的计时器,只有当用户停止输入超过 300 毫秒时,search 函数才会被执行。

让我们一步一步解析这个过程:

  1. 函数定义:首先,我们定义了一个 debounce 函数。这个函数接受两个参数:func 是我们希望防抖的实际函数,wait 是我们设定的等待时间(单位:毫秒)。
  2. 变量声明:在 debounce 函数内部,我们声明了一个 timeout 变量,用于保存定时器的 ID。
  3. 返回函数debounce 函数返回一个新的函数,这个新函数会在每次事件触发时调用。
  4. 清除定时器:每次事件触发时,首先会清除之前的定时器,确保之前的事件处理不会被执行。
  5. 设置定时器:然后,设置一个新的定时器,在等待时间 wait 之后执行 func

通过这种方式,我们确保了在用户停止输入超过指定时间后,才会执行实际的搜索操作。

什么是节流?

与防抖不同,节流的核心思想是控制函数执行的频率。在一定时间内,不管事件触发了多少次,处理器只会执行一次。节流机制可以保证在指定的时间间隔内函数只会被执行一次,从而限制了事件处理器的执行频率。

节流在一些需要持续响应用户操作的场景中尤为重要。例如,在窗口滚动事件中,如果不加以限制,滚动事件处理器会在用户滚动页面时被频繁触发,从而影响性能。而节流可以让我们在用户滚动过程中,每隔一定时间处理一次滚动事件,从而提升页面的流畅度。

节流的实现

假设我们有一个函数 handleScroll,它会在用户滚动时触发:

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

const handleScroll = throttle(() => {
    console.log('Handling scroll');
}, 200);

window.addEventListener('scroll', handleScroll);

在上面的代码中,throttle 函数接收两个参数:需要节流的函数 func 和时间间隔 wait。每次滚动事件触发时,函数会判断距离上次执行的时间是否超过了指定的时间间隔 wait,如果是,则执行函数 handleScroll,否则忽略此次事件。

  1. 函数定义:首先,我们定义了一个 throttle 函数。这个函数接受两个参数:func 是我们希望节流的实际函数,wait 是我们设定的时间间隔(单位:毫秒)。
  2. 变量声明:在 throttle 函数内部,我们声明了一个 lastTime 变量,用于保存上次函数执行的时间。
  3. 返回函数throttle 函数返回一个新的函数,这个新函数会在每次事件触发时调用。
  4. 获取当前时间:每次事件触发时,首先获取当前时间 now
  5. 判断时间间隔:然后,判断当前时间与上次执行时间 lastTime 之间的间隔是否超过了 wait
  6. 执行函数:如果间隔时间超过了 wait,则更新 lastTime 并执行 func

通过这种方式,我们确保了在指定的时间间隔内,实际的滚动处理函数只会被执行一次。

防抖的应用场景
  1. 搜索输入框:用户在输入关键词时,只在用户停止输入一定时间后发送请求,从而减少不必要的请求次数。
  2. 表单验证:用户在输入表单时,只在用户停止输入一定时间后进行表单验证,从而减少不必要的验证次数。
节流的应用场景
  1. 滚动事件:用户滚动页面时,每隔一定时间处理一次滚动事件,从而提升页面的流畅度。
  2. 调整窗口大小:用户调整窗口大小时,每隔一定时间更新窗口布局,从而避免频繁的重绘和重排。
  3. 按钮点击:用户点击按钮时,每隔一定时间响应一次点击事件,从而避免重复点击带来的问题。
  4. 窗口拖拽:用户拖拽窗口时,每隔一定时间更新窗口位置,从而提升拖拽的流畅度。
调整窗口大小的节流实现

调整窗口大小时,我们希望实时更新窗口的布局和内容,但又不希望因为过于频繁的事件触发影响性能。这里我们可以使用节流来控制更新频率:

function throttle(func, wait) {
    let timeout = null;
    return function(...args) {
        if (!timeout) {
            timeout = setTimeout(() => {
                timeout = null;
                func.apply(this, args);
            }, wait);
        }
    };
}

const handleResize = throttle(() => {
    console.log('Resizing window');
    // 处理窗口大小调整的逻辑
}, 200);

window.addEventListener('resize', handleResize);

在这个例子中,handleResize 函数每隔 200 毫秒执行一次,而不是在每次调整窗口大小时都执行,从而减少了不必要的计算,提升了性能。

防抖与节流的区别与联系

防抖和节流虽然都是为了优化高频事件,但它们的实现原理和应用场景有所不同。

  • 防抖:在一定时间内,只执行最后一次操作。适用于需要减少频繁操作的场景。
  • 节流:在一定时间内,只执行一次操作。适用于需要控制执行频率的场景。

虽然防抖和节流的实现方式不同,但它们的最终目的都是为了提升性能和用户体验。在实际开发中,我们可以根据具体需求选择合适的技术来优化页面性能。

总结

防抖和节流是前端开发中常用的两种性能优化技术。它们通过不同的方式来减少高频事件带来的性能问题,从而提升页面的响应速度和流畅度。理解并合理应用防抖和节流,可以显著改善用户体验,让我们的应用更加高效、流畅。在实际开发中,我们需要根据具体场景选择合适的技术,并不断优化我们的代码,以达到最佳的性能表现。