从0开始学习的防抖与节流

刚学前端的小伙伴们,是不是经常遇到这样的情况?比如在输入框里打字时,刚敲一个字母页面就疯狂 "跳动";或者拖动窗口调整大小时,整个页面像卡住了一样反应很慢。这些其实都是因为浏览器遇到了「高频触发的事件」------像输入框打字、窗口缩放、页面滚动这类操作,每秒可能会触发几十次甚至上百次事件,如果放任不管,不仅页面会卡顿得像 "幻灯片",服务器也会被频繁的请求 "压到崩溃",用户体验直接 "翻车"!

别慌!今天就来教大家两个超实用的前端「性能优化小妙招」------节流防抖

一、为什么要学习防抖和节流?

想象一下你在淘宝搜索「周杰伦专辑」,每输入一个字都触发一次搜索请求,结果页面疯狂刷新,体验极差不说,服务器也会被压垮。这就是高频事件的威力 ------每秒触发几十次甚至上百次的事件,会让页面卡顿、服务器过载,用户体验直接崩盘

这时候,防抖和节流就派上用场了。它们是前端性能优化的核心武器,能把高频事件的触发次数从「洪水」变成「细流」。

二、防抖

防抖的核心思想是:在事件被触发后,等待一段时间再执行回调函数。如果在这段时间内事件又被触发,则重新计时。只有当用户停止触发事件后,才会执行回调。

用代码解释就是:

  • 事件触发时,先清除之前的定时器
  • 重新设置一个定时器,延迟 t 毫秒执行函数
  • 如果在 t 毫秒内再次触发事件,就重复上述步骤

这样一来,只有最后一次触发事件后的 t 毫秒才会执行函数

js 复制代码
function debounce(fn, t) {
    let timer = null;
    return function(...args) { 
        clearTimeout(timer); 
        timer = setTimeout(function () {
            fn.apply(this, args); 
        }, t);
    };
}

关键点解析

  1. 闭包保存定时器timer 变量被闭包捕获,多次触发事件时共享同一个定时器
  2. this 指向问题 :通过 apply(this, arguments) 确保函数执行时 this 指向正确
  3. 参数传递arguments 收集事件参数,传递给目标函数

防抖的实际效果怎么样,来比对一下就知道了

js 复制代码
<input type="text" id="search-input">

<script>
  const searchInput = document.getElementById('search-input');
  
  // 原始高频触发版本
  searchInput.addEventListener('input', () => {
    console.log('请求搜索:', searchInput.value);
  });
  
  // 防抖优化版本
  searchInput.addEventListener('input', debounce(() => {
    console.log('实际搜索:', searchInput.value);
  }, 300));
</script>

朋友们可以自测一下。

来看一个实时搜索框的例子:

js 复制代码
const searchInput = document.getElementById('search');
searchInput.addEventListener('input', debounce(function (e) {
    console.log('发送请求:', e.target.value);
}, 500));

在这个例子中,用户输入内容时,只有在停止输入500毫秒后才会触发一次请求。

三、节流

节流的核心思想是:在一定时间间隔内,只执行一次回调函数。无论事件触发频率多高,回调函数都会按照固定时间间隔执行

如果觉得这段话不容易理解,就想一想游戏的技能冷却:释放技能后,必须等待 10 秒才能再次释放。期间无论按多少次技能键,都只有第一次有效。这样的机制就叫做节流。

用代码解释就是:

  • 记录上一次函数执行的时间
  • 每次触发事件时,计算当前时间与上次执行时间的差值
  • 如果差值大于等于 t 毫秒,就执行函数,并更新上次执行时间

这样一来,函数每隔 t 毫秒至少执行一次

话不多说,直接上代码:

js 复制代码
//时间戳版
function throttle(fn, t) {
    let lastTime = 0;
    return function(...args) {
        const now = Date.now();
        if (now - lastTime >= t) {
            fn.apply(this, args);
            lastTime = now;
        }
    };
}

关键点解析

  1. 时间戳控制频率 :通过 now - lastTime 判断是否达到执行间隔
  2. 立即执行:首次触发事件时立即执行函数
  3. 参数传递 :同样通过 apply 传递参数和绑定 this

节流还有另一种实现版本(定时器版)

js 复制代码
function throttle(fn, t) {
    let timer = null;
    return function(...args) { 
        if (!timer) {
            timer = setTimeout(function() {
                fn.apply(this, args); 
                timer = null;
            }, t);
        }
    };
}

两种版本区别

  • 时间戳版:首次触发立即执行,适合需要立即响应的场景(如滚动加载)
  • 定时器版:延迟执行,适合需要固定间隔的场景(如动画更新)

来看一个不用防抖与使用防抖的对比案例

javascript 复制代码
// 监听滚动事件
window.addEventListener('scroll', throttle(() => {
  console.log('滚动位置:', window.scrollY);
}, 200));

// 原始版本滚动时疯狂输出
// 节流后每200ms最多输出一次

四、防抖 vs 节流------到底该选谁?

特性 防抖(Debounce) 节流(Throttle)
核心思想 等事件停止触发后执行最后一次 按固定频率执行,忽略中间触发
典型场景 搜索、表单验证、按钮防重复提交 滚动加载、窗口缩放、鼠标移动追踪
延迟时间 事件停止后延迟 t 毫秒执行 事件触发后立即执行,然后每隔 t 毫秒执行一次
函数执行次数 1 次(最后一次触发) 多次(按频率执行)

举个栗子🌰

  • 打字时用防抖:等用户停手再搜索
  • 跑步时用节流:每隔 1 秒记录一次位置

五、防抖和节流的进阶技巧

在复杂场景中,可以先节流再防抖,实现「先节流控制频率,再防抖处理最终状态」。例如:

js 复制代码
// 先节流(200ms)再防抖(300ms)
function combined(fn, throttleTime, debounceTime) {
    const throttled = throttle(fn, throttleTime);
    const debounced = debounce(throttled, debounceTime);
    return debounced;
}

还有一个技巧是取消防抖,提供 cancel 方法手动清除定时器:

js 复制代码
function debounce(fn, t) { 
    let timer; 
    const debouncedFn = function (...args) { 
        clearTimeout(timer); 
        timer = setTimeout(() => { 
            fn.apply(this, args); 
        }, t); 
    }; 
    debouncedFn.cancel = function () { 
        if (timer) { 
            clearTimeout(timer); 
            timer = null; 
        } 
    }; 
    return debouncedFn; 
} 

// 示例 search 函数
function search() {
    console.log('执行搜索操作');
}

// 使用 
const searchFn = debounce(fetchSearch, 300); 
// 取消防抖 
searchFn.cancel(); 

六、为什么推荐使用 Lodash?

虽然手写防抖节流能加深理解,但实际项目中推荐直接使用 Lodash 的 _.debounce_.throttle,因为它们:

  1. 功能更完善:支持立即执行、leading/trailing 控制、参数透传等
  2. 性能更优:经过大厂工程师优化,稳定性高
  3. 代码更简洁:一行代码搞定,减少重复造轮子

Lodash 使用示例

js 复制代码
import { debounce, throttle } from 'lodash';

// 防抖:用户停止输入300ms后执行
const searchFn = debounce((query) => {
    console.log('搜索:', query);
}, 300);

// 节流:每隔1秒执行一次
const scrollFn = throttle((pos) => {
    console.log('滚动位置:', pos);
}, 1000);

实际上Lodash还支持更多配置,例如:

js 复制代码
// leading: true 表示首次触发时立即执行
// trailing: true 表示结束时执行一次
const searchFn = debounce((query) => {
    console.log('搜索:', query);
}, 300, { 
    leading: false,  
    trailing: true   
});

通过合理运用防抖和节流,你可以将高频事件的触发次数从洪水猛兽变为涓涓细流,让页面更流畅,系统更稳定。

希望本文对大家有所帮助,如果有什么问题,欢迎在评论区指出修正!

相关推荐
一只鱼^_2 分钟前
用JS实现植物大战僵尸(前端作业)
javascript·css·vscode·游戏引擎·游戏程序·html5·动画
魔云连洲3 分钟前
使用 SASS 与 CSS Grid 实现鼠标悬停动态布局变换效果
前端·css·sass
tiandyoin2 小时前
调教 DeepSeek - 输出精致的 HTML MARKDOWN
前端·html
Electrolux4 小时前
【使用教程】一个前端写的自动化rpa工具
前端·javascript·程序员
赵大仁5 小时前
深入理解 Pinia:Vue 状态管理的革新与实践
前端·javascript·vue.js
小小小小宇5 小时前
业务项目中使用自定义Webpack 插件
前端
小小小小宇6 小时前
前端AST 节点类型
前端
小小小小宇6 小时前
业务项目中使用自定义eslint插件
前端
babicu1236 小时前
CSS Day07
java·前端·css
小小小小宇6 小时前
业务项目使用自定义babel插件
前端