前端性能优化必学技能:手撕JavaScript「节流」与「防抖」,彻底弄懂!
摘要: 滚动加载、按钮提交、搜索框建议……这些场景离不开它俩。
正文:
朋友们,你是否遇到过这些让人头疼的问题?
-
一个按钮被用户疯狂点击,提交了N次重复表单。❌
-
页面滚动事件疯狂触发,复杂计算导致页面卡顿。❌
-
搜索框输入时,每敲一个字就发一次请求,服务器压力山大。❌
别担心!今天要讲的 「节流(Throttle)」 和 「防抖(Debounce)」 ,就是专门治这毛病的“神医”!💊

这不仅是日常开发中的性能优化利器,更是前端面试中高频得不能再高频的经典考题。
本文将带你从概念到手写实现,彻底弄懂它们!文末还准备了两函数的终极版代码,欢迎收藏备用。
📚 一、概念:它俩到底是啥?
想象一下场景:
1. 防抖 (Debounce) - “等你说完我再动”
例子:电梯门。电梯不会有人一进就关,而是会等一段时间,如果一直有人进,门就会一直保持开着,直到没人进了(等待时间到了),最后才关门。
核心:事件被触发后,在等待一段时间内没有再触发,回调才会执行。如果在这段等待时间内事件又被触发,则重新开始计时。
2. 节流 (Throttle) - “我说了算,每秒只能一次”
例子:游戏里的技能冷却。你按快捷键放技能,即使你疯狂连按,技能也只会按固定的频率释放一次,直到冷却结束。
核心:保证在一定时间间隔内,回调函数只执行一次。
为了帮你更好理解,我画了一张它们工作流程的区别图:
如图所示,在事件持续触发时:
-
节流 会像心跳一样,每隔固定时间就有规律地响应一次。
-
防抖 则会在事件停止触发后,再延迟一段时间才最终响应一次。
💻 二、手把手代码实现
光说不练假把式,我们来手写一个增强版的节流和防抖函数,它支持配置是否立即执行。
1. 防抖函数 (Debounce)
-
/** * 防抖函数 * @param {Function} func 要执行的函数 * @param {number} wait 等待时间(毫秒) * @param {boolean} immediate 是否立即执行 * @return {Function} */ function debounce(func, wait, immediate = false) { let timeout = null; return function (...args) { const context = this; // 如果已有定时器,清除它,重新开始计时 if (timeout) clearTimeout(timeout); if (immediate) { // 如果尚未到等待时间,callNow 为 false,不执行 // 如果已经过了等待时间,callNow 为 true,可以执行 const callNow = !timeout; // 设置新的定时器,wait 时间后重置 timeout timeout = setTimeout(() => { timeout = null; }, wait); // 如果可以立即执行,则调用函数 if (callNow) func.apply(context, args); } else { // 标准防抖:延迟执行 timeout = setTimeout(() => { func.apply(context, args); }, wait); } }; }
2. 节流函数 (Throttle)
我们用 Date.now() 和 setTimeout 结合的方式,实现一个既能立即执行第一次,又能在结束后执行最后一次的增强版节流。
/**
* 节流函数
* @param {Function} func 要执行的函数
* @param {number} wait 等待时间(毫秒)
* @return {Function}
*/
function throttle(func, wait) {
let previous = 0; // 记录上一次执行的时间戳
let timeout = null;
return function (...args) {
const context = this;
const now = Date.now();
const remaining = wait - (now - previous); // 计算剩余时间
// 如果剩余时间小于等于0,说明可以执行了
if (remaining <= 0) {
// 如果存在定时器,清除掉,因为我们要立即执行
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now; // 更新上一次执行时间
func.apply(context, args);
} else if (!timeout) {
// 如果剩余时间还没到,且没有设置定时器,则设置定时器在剩余时间后执行
timeout = setTimeout(() => {
previous = Date.now(); // 更新上一次执行时间
timeout = null;
func.apply(context, args);
}, remaining);
}
};
}
🚀 三、应用场景:你用对地方了吗?
防抖 (Debounce) 用在:
-
搜索框输入联想:等用户停止输入500毫秒后再请求搜索建议,避免请求过于频繁。
-
窗口
resize事件:等待用户调整浏览器窗口结束后,再计算布局,避免频繁重排重绘。 -
文本编辑器自动保存:等用户停止编辑几秒后进行保存。
节流 (Throttle) 用在:
-
按钮频繁点击(如提交、抢购):确保1秒内只提交一次,防止重复请求。
-
页面滚动加载更多(无限滚动):滚动时每隔500ms检查一次位置,而不是每滚动1px都检查。
-
游戏中的按键事件:确保技能按冷却时间释放。
简单记:防抖关心「最后一步」,节流关心「固定节奏」。
📦 四、终极懒人包
如果你不想自己手写,常用的工具库已经帮你准备好了!
-
Lodash:
_.debounce(func, [wait=0], [options={}])和_.throttle(func, [wait=0], [options={}]),功能非常强大。 -
Underscore.js: 同样提供了
_.debounce和_.throttle。
在项目中直接引入使用,是效率最高的选择。
❤️ 五、总结与互动
总结一下:
-
防抖:连续事件结束后只执行一次。
-
节流:连续事件每隔一段时间执行一次。
搞懂它们,不仅能让你在面试中游刃有余,更能实实在在地提升你开发应用的性能和用户体验。
#互动话题#
你在实际项目中用过节流或防抖吗?用在了什么有趣的场景?或者遇到过什么坑?
在评论区分享你的经验吧!
(关注下方微信公众号获取更多原创作品完整代码)

更多推荐
所有评论(0)