刚学前端的小伙伴们,是不是经常遇到这样的情况?比如在输入框里打字时,刚敲一个字母页面就疯狂 "跳动";或者拖动窗口调整大小时,整个页面像卡住了一样反应很慢。这些其实都是因为浏览器遇到了「高频触发的事件」------像输入框打字、窗口缩放、页面滚动这类操作,每秒可能会触发几十次甚至上百次事件,如果放任不管,不仅页面会卡顿得像 "幻灯片",服务器也会被频繁的请求 "压到崩溃",用户体验直接 "翻车"!
别慌!今天就来教大家两个超实用的前端「性能优化小妙招」------节流 和防抖
一、为什么要学习防抖和节流?
想象一下你在淘宝搜索「周杰伦专辑」,每输入一个字都触发一次搜索请求,结果页面疯狂刷新,体验极差不说,服务器也会被压垮。这就是高频事件的威力 ------每秒触发几十次甚至上百次的事件,会让页面卡顿、服务器过载,用户体验直接崩盘。
这时候,防抖和节流就派上用场了。它们是前端性能优化的核心武器,能把高频事件的触发次数从「洪水」变成「细流」。
二、防抖
防抖的核心思想是:在事件被触发后,等待一段时间再执行回调函数。如果在这段时间内事件又被触发,则重新计时。只有当用户停止触发事件后,才会执行回调。
用代码解释就是:
- 事件触发时,先清除之前的定时器
- 重新设置一个定时器,延迟
t
毫秒执行函数 - 如果在
t
毫秒内再次触发事件,就重复上述步骤
这样一来,只有最后一次触发事件后的 t
毫秒才会执行函数。
js
function debounce(fn, t) {
let timer = null;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(function () {
fn.apply(this, args);
}, t);
};
}
关键点解析:
- 闭包保存定时器 :
timer
变量被闭包捕获,多次触发事件时共享同一个定时器 - this 指向问题 :通过
apply(this, arguments)
确保函数执行时this
指向正确 - 参数传递 :
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;
}
};
}
关键点解析:
- 时间戳控制频率 :通过
now - lastTime
判断是否达到执行间隔 - 立即执行:首次触发事件时立即执行函数
- 参数传递 :同样通过
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
,因为它们:
- 功能更完善:支持立即执行、leading/trailing 控制、参数透传等
- 性能更优:经过大厂工程师优化,稳定性高
- 代码更简洁:一行代码搞定,减少重复造轮子
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
});
通过合理运用防抖和节流,你可以将高频事件的触发次数从洪水猛兽变为涓涓细流,让页面更流畅,系统更稳定。
希望本文对大家有所帮助,如果有什么问题,欢迎在评论区指出修正!