前端防重需服务端协同:按钮禁用须在finally/catch中恢复,防抖需透传this和event,节流不适用表单提交,请求去重依赖唯一标识与后端幂等设计。

按钮禁用后不恢复,是常见错误源头
很多开发者在 submit 事件里禁用按钮,却没处理失败场景下的恢复逻辑。结果用户提交失败(如网络超时、401),按钮永远灰着,页面卡死——这不是防重,是阻塞。
必须在 finally 或 Promise 的 .catch() 和 .then() 两个分支都重置状态:
- 禁用前先保存原始文本,避免多次点击后按钮文字错乱
- 不要只禁用一个
button,用form.querySelectorAll('button[type="submit"], input[type="submit"]')批量处理 - 禁用不能替代服务端校验,回车触发表单提交时,禁用无效
防抖函数传参容易漏掉 this 和 event
直接把 debounce(fetchData, 300) 绑定到 onclick,fetchData 里取不到 this 或事件对象。原因:防抖返回的是新函数,原调用上下文丢失。
正确做法是显式绑定或包装一层:
立即学习“前端免费学习笔记(深入)”;
- 用箭头函数包裹:
debounce(() => fetchData(url), 300) - 或在防抖内部用
fn.apply(this, arguments)透传上下文(注意arguments在箭头函数中不可用) - 搜索类场景慎用防抖:如果用户输得慢(比如每字间隔 >300ms),每次输入都会发请求,反而增加压力
节流不适合表单提交,但适合高频按钮点击
throttle 保证固定间隔最多执行一次,表面看能“限频”,但它不感知请求是否完成。比如用户点两次“加载更多”,第一次请求还没返回,第二次就被丢弃——体验断层,且无法区分成功/失败。
它真正适用的场景有限:
- 滚动加载(
scroll事件) - 鼠标移动轨迹采样(
mousemove) - 非关键性 UI 反馈(如打字光标闪烁)
- 绝对不要用于表单提交、支付、下单等强状态操作
请求级去重必须依赖唯一标识 + 后端配合
前端靠 pending Map 做请求守卫(如 new Map().set(key, promise))看似完美,但 key 构建极易出错。常见坑:
-
JSON.stringify({ a: 1, b: 2 })和JSON.stringify({ b: 2, a: 1 })不等价 → key 不一致,守卫失效 - 没标准化 URL 参数顺序、没过滤空值或 undefined 字段
- 没包含请求体(
body)哈希,POST 请求完全绕过 - 最关键的:该方案纯前端,刷新页面即失效;必须和服务端
submit_token或幂等 key(如Idempotency-Keyheader)联动才可靠
最易被忽略的一点:防重不是前端单方面能闭环的事。按钮禁用、防抖、pending map 都只是降低请求数量的“减法”,而服务端 token 校验、幂等设计、PRG 跳转才是真正的“保险丝”。没后端协同,前端所有努力都可能被绕过或失效。



















