防抖与节流:优化高频事件的两种利器
一、引言
在现代前端开发中,用户交互的实时性和流畅性至关重要。然而,某些高频事件(如输入框输入、窗口缩放、滚动等)可能会频繁触发回调函数,导致性能问题。例如,以下代码中的输入框会在每次输入时触发 oninput
事件:
<div>
请输入你想搜索的内容: <input type="text" name="search" id="search">
</div>
<script>
let input = document.querySelector('#search');
input.oninput = function () {
console.log("===> input回调事件触发了" + this.value);
}
</script>
用户每输入一个字符,控制台会输出日志,这可能会导致页面卡顿或请求过多。正确的方式应该是用户输入完成后,才触发一次即可。为了解决这类问题,我们可以使用 防抖(Debounce) 和 节流(Throttle) 技术来优化高频事件的触发频率。
二、基本概念
1. 防抖(Debounce)
-
定义与核心思想
防抖的核心思想是:在事件被触发后,等待一段时间(如 500ms),如果在这段时间内没有再次触发事件,则执行回调函数。如果在这段时间内事件再次被触发,则重新计时。 -
类比解释
想象电梯关门的场景:当有人进入电梯时,电梯门不会立即关闭,而是等待一段时间(如 5 秒)。如果在这段时间内又有人进入,电梯会重新等待 5 秒,直到没有人进入后才关门。 -
适用场景
- 搜索框输入建议(等待用户停止输入后再请求)
- 窗口缩放事件(等待用户停止调整窗口大小后再重新布局)
2. 节流(Throttle)
-
定义与核心思想
节流的核心思想是:在事件被触发后,固定一段时间(如 1000ms)内只执行一次回调函数。无论事件触发多少次,回调函数都会按照固定的时间间隔执行。 -
类比解释
想象游戏中的技能冷却时间(CD):释放技能后,必须等待一段时间才能再次释放,无论玩家按了多少次技能键。 -
适用场景
- 滚动加载更多内容(每隔一段时间检查滚动位置)
- 鼠标移动事件(每隔一段时间更新鼠标位置)
三、Lodash 库的实现
Lodash 是一个流行的 JavaScript 工具库,提供了现成的防抖和节流函数,使用起来非常方便。
1. _.debounce()
方法
- 功能:创建一个防抖函数,延迟执行回调。
- 参数:
func
:需要防抖的回调函数。wait
:等待时间(单位:毫秒)。options
:可选配置(如leading
和trailing
)。
- 示例:
input.oninput = _.debounce(function() {
console.log("防抖后的输入值:" + this.value);
}, 500);
2. _.throttle()
方法
- 功能:创建一个节流函数,固定时间间隔执行回调。
- 参数:
func
:需要节流的回调函数。wait
:时间间隔(单位:毫秒)。options
:可选配置(如leading
和trailing
)。
- 示例:
window.onscroll = _.throttle(function() {
console.log("滚动事件触发,当前位置:" + window.scrollY);
}, 1000);
四、对比与选择
1. 防抖 vs 节流的差异
-
执行时机:
- 防抖:在事件停止触发后执行。
- 节流:在固定时间间隔内执行。
-
适用场景:
- 防抖:适合处理最终状态(如输入框的最终值)。
- 节流:适合处理过程状态(如滚动事件)。
2. 如何选择
- 如果需要响应最终结果,选择防抖。
- 如果需要均匀分布事件触发,选择节流。
3. 性能影响
- 防抖和节流都能有效减少回调函数的执行次数,从而提升性能。
- 防抖更适合减少不必要的计算或请求,节流更适合平滑高频事件的触发。
五、结合示例代码的实战
1. 原始代码的问题
以下代码中,输入框的 oninput
事件和按钮的 onclick
事件会频繁触发,可能导致性能问题:
- 输入框每次输入都会触发回调,控制台输出大量日志(使用防抖解决)。
let input = document.querySelector('#search');
input.oninput = function () {
console.log("===> input回调事件触发了" + this.value);
}
- 按钮每次点击都会触发回调,计数器快速增加(使用节流解决)。
let btn = document.querySelector('#btn');
let span = document.querySelector('span');
let count = 0;
btn.onclick = function () {
count++;
span.innerHTML = count;
}
2. 防抖改造示例
使用 Lodash 的 _.debounce()
方法优化输入框事件:
- 延迟 500ms 后执行回调。
- 如果用户在 500ms 内继续输入,则重新计时。
input.oninput = _.debounce(function () {
console.log("防抖后的输入值:" + this.value);
}, 500);
最终效果:快速输入时,控制台只会在用户停止输入 500ms 后输出最终的输入值。
3. 节流改造示例
使用 Lodash 的 _.throttle()
方法优化按钮点击事件:
- 每隔 1000ms 最多执行一次回调。
- 无论用户点击多少次,计数器每秒最多增加一次。
btn.onclick = _.throttle(function () {
count++;
span.innerHTML = count;
}, 1000);
最终效果:快速点击按钮时,计数器每秒最多增加一次。
4. 完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>防抖与节流示例</title>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
</head>
<body>
<div>
<h1>防抖</h1>
请输入你想搜索的内容: <input type="text" name="search" id="search">
</div>
<div>
<h1>节流</h1>
<p>我是计数器 <span>0</span></p>
<button id="btn">点我 +1 </button>
</div>
<script>
// 防抖部分
let input = document.querySelector('#search');
input.oninput = _.debounce(function () {
console.log("防抖后的输入值:" + this.value);
}, 500);
// 节流部分
let btn = document.querySelector('#btn');
let span = document.querySelector('span');
let count = 0;
btn.onclick = _.throttle(function () {
count++;
span.innerHTML = count;
}, 1000);
</script>
</body>
</html>
通过本示例,我们可以看到:
- 防抖 适用于需要等待用户操作结束后再执行的场景(如输入框搜索)。
- 节流 适用于需要限制操作频率的场景(如按钮点击、滚动事件)。
这两种技术都能有效优化高频事件的性能问题,提升用户体验。