防抖与节流:前端性能优化的实用技巧

49 阅读5分钟

防抖与节流:前端性能优化的实用技巧

在前端开发中,我们经常会遇到一些频繁触发的事件,比如输入框的keyup、页面滚动的scroll、窗口大小改变的resize等。如果这些事件绑定的处理函数执行频繁且逻辑复杂,很可能会导致页面性能下降,出现卡顿等问题。防抖(Debounce)和节流(Throttle)就是两种解决这类问题的常用优化手段。

防抖(Debounce):等待最后一次触发后执行

核心思想

防抖的核心逻辑是:在一定时间内,多次触发同一事件时,只执行最后一次触发的处理函数。简单来说,就是 "等用户停止操作一段时间后再执行",避免中间频繁触发的无效操作。

应用场景

防抖适用于需要 "等待用户完成操作后再执行" 的场景,典型例子包括:

  • 搜索建议(如百度的 Ajax Suggest):用户在输入框快速打字时,不需要每输入一个字符就立即发送请求,而是等用户暂停输入后再发送,减少不必要的网络请求。
  • 代码提示(Code Suggest):类似搜索建议,避免输入过程中频繁计算提示内容,平衡性能与用户体验。
  • 表单验证:用户输入过程中不需要实时验证,可等待输入暂停后再验证。

实现方式

防抖的实现主要依赖定时器闭包

  • 用闭包保存定时器 ID,确保多次触发时能访问到同一个定时器。
  • 每次触发事件时,先清除之前的定时器,再重新设置一个新的定时器。
  • 只有当事件停止触发的时间超过设定的延迟时间后,才会执行目标函数。

以下是防抖函数的实现代码:

// 防抖函数:fn为需要执行的函数,delay为延迟时间(毫秒)
function debounce(fn, delay) {
    let timerId; // 闭包保存定时器ID
    const that = this; // 保存上下文

    return function(args) {
        // 每次触发时,清除之前的定时器
        if (timerId) clearTimeout(timerId);
        // 重新设置定时器,延迟执行目标函数
        timerId = setTimeout(function() {
            fn.call(that, args); // 确保函数执行上下文正确
        }, delay);
    };
}

使用示例(输入框场景):

// 模拟Ajax请求
function ajax(content) {
    console.log('发送请求:', content);
}

// 创建防抖处理后的函数(延迟500ms)
const debounceAjax = debounce(ajax, 500);

// 给输入框绑定事件
const input = document.getElementById('debounce');
input.addEventListener('keyup', function(e) {
    debounceAjax(e.target.value); // 只有停止输入500ms后才会触发请求
});

节流(Throttle):固定间隔内只执行一次

核心思想

节流的核心逻辑是:在一定时间内,无论事件触发多少次,只执行一次处理函数。就像游戏中的射速限制,即使一直按住射击键,也只会按固定频率发射子弹。

应用场景

节流适用于需要 "按固定频率执行" 的场景,典型例子包括:

  • 滚动加载:页面滚动时,不需要每次滚动都检测是否到达底部,而是每隔固定时间检测一次,减少计算开销。
  • 窗口 resize 事件:调整窗口大小时,固定间隔执行布局调整逻辑,避免频繁重排。
  • 鼠标移动 / 拖拽:跟踪鼠标位置时,固定间隔更新,平衡精度与性能。

实现方式

节流的实现通常通过时间戳定时器结合闭包:

  • 用闭包保存上次执行的时间戳。
  • 每次触发事件时,计算当前时间与上次执行时间的差值。
  • 如果差值大于设定的间隔时间,立即执行目标函数并更新时间戳;否则,设置定时器在剩余时间后执行(确保最后一次触发能被执行)。

以下是节流函数的实现代码:

// 节流函数:fn为需要执行的函数,delay为间隔时间(毫秒)
function throttle(fn, delay) {
    let lastTime; // 闭包保存上次执行时间戳
    let deferTimer; // 闭包保存延迟执行的定时器

    return function() {
        const that = this; // 保存上下文
        const args = arguments; // 保存参数
        const now = +new Date(); // 当前时间戳(毫秒)

        // 如果上次执行过且未到间隔时间
        if (lastTime && now < lastTime + delay) {
            clearTimeout(deferTimer); // 清除之前的延迟定时器
            // 设置新的延迟定时器,确保最后一次触发能执行
            deferTimer = setTimeout(function() {
                lastTime = now;
                fn.apply(that, args);
            }, delay);
        } else {
            // 到达间隔时间,立即执行
            lastTime = now;
            fn.apply(that, args);
        }
    };
}

使用示例(输入框场景):

// 模拟Ajax请求
function ajax(content) {
    console.log('发送请求:', content);
}

// 创建节流处理后的函数(间隔500ms)
const throttleAjax = throttle(ajax, 500);

// 给输入框绑定事件
const input = document.getElementById('throttle');
input.addEventListener('keyup', function(e) {
    throttleAjax(e.target.value); // 每隔500ms最多执行一次请求
});

防抖与节流的区别

虽然防抖和节流都是为了减少函数执行次数、优化性能,但核心区别在于:

  • 防抖:关注 "停止触发后执行",在设定时间内多次触发只会执行最后一次(如等待用户输入结束后再请求)。
  • 节流:关注 "固定间隔执行",在设定时间内无论触发多少次,都只执行一次(如按固定频率检测滚动位置)。

简单来说:防抖是 "等平静后再行动",节流是 "按节奏行动"。

总结

防抖和节流是前端性能优化中处理高频事件的重要手段,它们通过合理控制函数执行时机,在不影响核心功能的前提下减少不必要的计算和请求,提升页面流畅度。实际开发中,需根据具体场景选择合适的方案:需要等待用户完成操作时用防抖,需要固定频率执行时用节流。