一、为什么要用到防抖和节流
在日常的前端开发中,我们经常需要处理用户的各种交互行为,比如滚动、点击、输入等。这些行为在特定场景下可能会触发非常频繁的事件,例如:用户在滚动条上快速拖动、用户在输入框中快速连续输入、窗口大小被不断调整。
频繁触发回调导致的大量计算会引发页面的抖动甚至卡顿。为了规避这种情况,我们需要一些手段来控制事件被触发的频率。就是在这样的背景下,throttle(事件节流)和 debounce(事件防抖)出现了。
二、防抖
1.什么是防抖
防抖的中心思想是:在事件被触发后,等待一段时间(这个时间由我们设定)。如果在等待期间事件再次被触发,则重新计时。只有当等待期间没有再次触发事件时,函数才会执行一次。
在某些 RPG 游戏中,有些强力技能设计为:按下技能键后,角色开始吟唱(2 秒),如果在吟唱过程中再次按键或被打断,吟唱重新开始,只有完整吟唱 2 秒后,技能才会释放。这也是防抖的应用:只有在最后一次按键后的等待时间内没有新的干扰,才会执行最终操作。
2.防抖的实现
理解了大致的思路,我们来实现一个防抖函数:
/**
* 防抖函数,用于限制函数的执行频率
*
* @param {Function} func - 需要防抖的函数
* @param {number} wait - 等待时间(毫秒),即停止触发事件后多少毫秒执行函数
* @return {Function} - 返回一个新的防抖函数
*/
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
// 延迟执行函数
const later = () => {
clearTimeout(timeout);
func(...args);
};
// 清除之前的定时器,重新设置新的定时器
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
3.防抖的应用
防抖在前端开发中有多种应用场景:
- 搜索框输入优化:当用户在搜索框中输入内容时,使用防抖可以等待用户输入完成后再发送请求,避免频繁的 API 调用。
- 表单验证:在用户填写表单时,可以使用防抖来延迟验证,直到用户停止输入一段时间后再进行验证。
- 窗口调整事件处理:当用户调整浏览器窗口大小时,使用防抖可以等待调整完成后再重新计算布局。
下面是一个简单的防抖应用示例:
const inputField = document.getElementById("inputField");
function handleInput(event) {
console.log("Input value:", event.target.value);
}
const debouncedHandleInput = debounce(handleInput, 1000);
inputField.addEventListener("input", debouncedHandleInput);
4.支持 immediate 模式
防抖函数的 immediate 模式是一种特殊的工作模式,与常规防抖不同,immediate 模式会在第一次触发事件时立即执行函数,而不是等待延迟结束后执行。在这种模式下,函数会在事件首次触发时立即执行,随后的连续触发会被忽略,直到延迟时间结束后才能再次触发执行。
/**
* 手动实现防抖函数
*
* @param {Function} fn 要被防抖处理的函数
* @param {number} delay 延迟执行的时间,单位为毫秒
* @param {boolean} immediate 是否在第一次触发时立即执行函数,默认为false
* @returns {Function} 返回一个防抖处理后的函数
*/
function debounce(fn, delay, immediate = false) {
let timer = null;
// 使用闭包保存定时器状态
return function (...args) {
const context = this;
// 每次调用都要清除之前的定时器,重新计时
if (timer) clearTimeout(timer);
if (immediate) {
const callNow = !timer;
timer = setTimeout(() => {
timer = null;
}, delay);
// 如果当前是第一次触发或者上一次触发后已经超过了延时时间,则立即执行函数
if (callNow) {
fn.apply(context, args);
}
} else {
timer = setTimeout(() => {
fn.apply(context, args);
}, delay);
}
};
}
三、节流
1.什么是节流
节流的中心思想是:在某个时间间隔内,无论事件触发多少次,函数都只执行一次。
在英雄联盟、Dota 等 MOBA 游戏中:每个技能都有冷却时间(如 10 秒),在冷却期间,无论玩家按下技能键多少次,技能都不会释放,只有等冷却结束后,才能再次释放技能,系统会在固定间隔后才接受新的技能释放请求。 这也是节流的体现:在规定的时间间隔内,无论触发多少次,都只执行一次操作。
2.节流的实现
理解了大致的思路,我们来实现一个节流函数:
/**
* 节流函数
* @param {Function} fn - 需要节流的回调函数
* @param {number} interval - 节流时间间隔(毫秒)
* @returns {Function} - 经过节流处理的新函数
*/
function throttle(fn, interval) {
// 记录上一次触发回调的时间
let last = 0;
return function () {
// 保存函数执行时的上下文和参数
let context = this;
let args = arguments;
// 获取当前时间戳
let now = +new Date();
// 检查是否达到节流时间间隔
if (now - last >= interval) {
last = now;
fn.apply(context, args);
}
};
}
3.节流的应用
节流在前端开发中有多种应用场景:
- 滚动事件优化:在处理页面滚动事件时,使用节流可以限制处理函数的执行频率,提高页面性能。
- 拖拽元素实现:在实现拖拽功能时,使用节流可以限制位置更新的频率,使拖拽更加流畅。
- 游戏中的按键处理:在游戏开发中,使用节流可以控制玩家操作的频率,如限制射击频率。
下面是一个简单的节流应用示例:
function handleScroll() {
console.log("Scroll event handled");
}
const throttledScroll = throttle(handleScroll, 2000);
window.addEventListener("scroll", throttledScroll);
4.支持 leading 和 trailing 两种执行模式
节流函数(Throttle)中的 leading 和 trailing 是两种控制执行时机的模式:leading 模式(首次执行模式)使函数在事件序列开始时立即执行,适合需要立即响应的场景,如按钮点击反馈;trailing 模式(延迟执行模式)则使函数在事件序列结束后才执行,使用最后一次触发的参数,适合等待用户操作完成后再响应的场景,如调整窗口大小后重新布局。
/**
* 节流函数
*
* @param {Function} fn 要节流的函数。
* @param {number} delay 延迟的毫秒数。
* @param {boolean} [leading=true] 是否在第一次调用时立即执行函数。
* @param {boolean} [trailing=false] 是否在最后一次调用后执行函数。
* @returns {Function} 返回一个新的节流函数。
*/
function throttle(fn, delay, leading = true, trailing = false) {
// 上次执行的时间戳
let lastExecTime = 0;
// 定时器变量
let timer = null;
return function (...args) {
const now = Date.now();
const context = this;
// 如果是第一次调用且不需要立即执行
if (!leading && lastExecTime === 0) {
lastExecTime = now;
}
const elapsed = now - lastExecTime;
if (elapsed >= delay) {
if (timer) {
clearTimeout(timer);
}
fn.apply(context, args);
lastExecTime = now;
} else if (trailing && !timer) {
timer = setTimeout(() => {
fn.apply(context, args);
// 如果leading为false,重置为0,这样下次调用会被视为首次调用
lastExecTime = leading ? Date.now() : 0;
timer = null;
}, delay - elapsed);
}
};
}
四、总结
节流和防抖都以闭包的形式存在,它们通过对事件对应的回调函数进行包裹、以自由变量的形式缓存时间信息,最后用 setTimeout 来控制事件的触发频率。
通过合理使用防抖和节流技术,可以有效提高 Web 应用的性能和用户体验,减少不必要的计算和网络请求,使应用响应更加流畅。