debounce 和 throttle 非浏览器原生 API,需手动实现或引入 Lodash;按钮点击防抖通过「清旧启新」确保最后一次点击后延迟执行,适用于表单提交等场景;节流则限制单位时间触发次数,适合音量调节等稳态反馈场景;须正确处理 this、event 和参数绑定,避免上下文丢失。

debounce 和 throttle 不是浏览器原生 API,必须自己实现或引入 Lodash 等库;直接绑定 onclick 无法生效,必须包裹后赋值。
按钮点击防抖:防止重复提交最常用方案
用户狂点提交按钮时,debounce 能确保只在最后一次点击后延迟执行——比如表单提交、支付确认等场景,避免后端收到一堆重复请求。
- 核心逻辑是「清旧启新」:每次点击都
clearTimeout上次定时器,再设新定时器 - 注意
this和arguments的绑定:不加fn.apply(this, arguments),回调里拿不到事件对象或上下文 - 别漏掉
timer = null(尤其在setTimeout回调里),否则闭包中残留的 timer 可能干扰下一次调用 - 示例:
button.onclick = debounce(() => { submitForm() }, 800)—— 用户连续点 5 次,仅第 5 次点击后 800ms 执行submitForm
按钮点击节流:限制单位时间内的最大触发次数
适合需要「稳态反馈」的按钮,比如音量调节、翻页、游戏技能释放——不是等用户停手,而是不管怎么点,每秒最多响应一次。
- 主流实现分两种:时间戳法(立即执行 + 时间差判断)和定时器法(首次触发设锁,到期解锁)
- 时间戳法更轻量,但首次点击立刻执行;定时器法首次点击要等 delay 才执行,更适合「防误触」场景
- 务必用
let _lastTime = 0初始化,不能用null或undefined直接参与减法,否则Date.now() - null得NaN,逻辑失效 - 示例:
button.onclick = throttle(() => { playSound() }, 1000)—— 用户从 0s 开始连点,只在 0s、1s、2s … 触发playSound
常见错误:this、event、参数全丢了
直接写 debounce(fn, 300) 并传入事件监听器,很容易让 fn 里的 this 指向 window,或收不到 event 参数。
立即学习“前端免费学习笔记(深入)”;
- 错误写法:
btn.addEventListener('click', debounce(handleClick, 300))——handleClick内部的this是window,event是undefined - 正确写法:在
debounce返回函数中显式传参,例如fn.apply(this, arguments)或用箭头函数 + 展开:fn(...arguments) - 更稳妥的做法是把 event 当显式参数接收:
debounce((e) => handleClick(e), 300),避免依赖this和隐式参数
要不要加 immediate 参数?看交互预期
标准 debounce 是“尾部执行”,但有些按钮(如“刷新”)希望首次点击立刻响应,之后才防抖——这就需要 immediate 开关。
- 加了
immediate: true后,逻辑变成:首次触发立即执行,后续触发进入等待;停止触发后不再补执行 - 实现上需拆成两个分支:用
!timeout && immediate判断是否立即执行,再统一管理定时器 - 注意:带
immediate的版本不能和原生addEventListener的 passive 选项混用,可能引发事件监听冲突 - 没特殊需求就别加——多数表单提交、搜索框都不需要首点即发,反而增加理解成本
真正容易被忽略的是闭包变量生命周期和事件参数透传。写完 debounce 函数后,一定要在真实按钮上测点击、console.log(event)、检查 this,而不是只跑 console.log(1) 这种空回调。



















