最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

前端防止重復(fù)支付解決方案完整記錄

 更新時(shí)間:2026年03月20日 10:46:08   作者:sophie旭  
前端方法能防止用戶誤操作,但不能完全依賴,因?yàn)榭梢酝ㄟ^(guò)工具繞過(guò)前端驗(yàn)證,這篇文章主要介紹了前端防止重復(fù)支付解決方案的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下

背景

這期并不是什么高大上的主題,但是對(duì)于支付業(yè)務(wù)卻是尤為重要,那就是如何在前端角度防止重復(fù)支付,在這邊把我的解決方案記錄下來(lái),也想剖析一下里面的細(xì)節(jié),同時(shí)也分享給大家。

解決方案

// 假設(shè)使用 lodash 的 throttle 函數(shù)
import { throttle } from 'lodash';

// 定義 loading 狀態(tài)(Vue 中可放在 data/setup 中)
let isPayLoading = false;

// 支付核心函數(shù)
const goToPay = async () => {
  // 1. Loading 狀態(tài)鎖:攔截重復(fù)請(qǐng)求
  if (isPayLoading) return;
  
  try {
    // 2. 開(kāi)啟 loading(按鈕置灰、顯示加載動(dòng)畫(huà))
    isPayLoading = true;
    
    // 3. 執(zhí)行支付請(qǐng)求(示例接口)
    const res = await fetch('/api/pay', {
      method: 'POST',
      body: JSON.stringify({ orderId: '123456' })
    });
    
    const data = await res.json();
    if (data.code === 200) {
      alert('支付成功!');
    } else {
      alert('支付失敗:' + data.msg);
    }
  } catch (err) {
    alert('支付請(qǐng)求異常:' + err.message);
  } finally {
    // 4. 無(wú)論成功/失敗,關(guān)閉 loading
    isPayLoading = false;
  }
};

// 節(jié)流包裝支付函數(shù):攔截快速點(diǎn)擊
const throttlePay = throttle(
  () => {
    goToPay();
  },
  2000,
  { leading: true, trailing: false }
);

// 支付按鈕點(diǎn)擊事件綁定這個(gè)節(jié)流后的函數(shù)
// <button onclick="throttlePay()" :disabled="isPayLoading">支付</button>

一、先理清整體邏輯

你這段代碼的核心是:

  • throttle 節(jié)流函數(shù)包裝支付觸發(fā)邏輯,設(shè)置 2000ms 內(nèi)只能觸發(fā)一次,且 leading: true(立即觸發(fā)第一次)、trailing: false(不觸發(fā)最后一次)。
  • 在被調(diào)用的 goToPay 內(nèi)部還加了 loading 狀態(tài)控制。 兩者結(jié)合形成了“雙重保險(xiǎn)”來(lái)防止重復(fù)支付,我們先拆解各自的作用,再分析組合的巧妙性。

二、防止重復(fù)支付的核心原因

重復(fù)支付的本質(zhì)是:用戶短時(shí)間內(nèi)多次點(diǎn)擊“支付”按鈕,導(dǎo)致多次觸發(fā)支付請(qǐng)求,后端可能接收到多個(gè)支付指令,最終造成重復(fù)扣款。而節(jié)流 + loading 的組合從不同維度阻斷了這個(gè)問(wèn)題:

1. 節(jié)流(throttle)的作用:阻斷“快速連續(xù)觸發(fā)”

  • 底層原因:節(jié)流函數(shù)的核心是「時(shí)間窗口控制」—— 設(shè)定 2000ms 為一個(gè)時(shí)間窗口,窗口內(nèi)無(wú)論觸發(fā)多少次,只會(huì)執(zhí)行一次(leading: true 保證第一次點(diǎn)擊立即響應(yīng),trailing: false 避免窗口結(jié)束后額外觸發(fā))。 比如用戶瘋狂點(diǎn)擊支付按鈕,1秒內(nèi)點(diǎn)了5次,節(jié)流會(huì)確保只有第一次點(diǎn)擊觸發(fā) goToPay,剩下4次直接被攔截,從「觸發(fā)頻率」上限制了重復(fù)請(qǐng)求。
  • 局限性:節(jié)流只控制“觸發(fā)次數(shù)”,但無(wú)法感知支付請(qǐng)求的「執(zhí)行狀態(tài)」(比如請(qǐng)求是否成功、是否還在處理中)。如果支付請(qǐng)求本身耗時(shí)超過(guò) 2000ms(比如網(wǎng)絡(luò)慢),節(jié)流窗口過(guò)期后,用戶再次點(diǎn)擊仍可能觸發(fā)重復(fù)請(qǐng)求。

2. Loading 控制的作用:阻斷“請(qǐng)求未完成時(shí)的觸發(fā)”

goToPay 中加 loading 通常是這樣的邏輯:

// 示例:goToPay 核心邏輯
let isLoading = false; // 全局/局部的 loading 狀態(tài)
function goToPay() {
  // 1. 如果正在加載中,直接返回,不執(zhí)行后續(xù)邏輯
  if (isLoading) return;
  
  // 2. 開(kāi)啟 loading(按鈕置灰、顯示加載動(dòng)畫(huà))
  isLoading = true;
  
  // 3. 執(zhí)行支付請(qǐng)求
  payRequest()
    .then(res => {
      // 支付成功邏輯
    })
    .catch(err => {
      // 支付失敗邏輯
    })
    .finally(() => {
      // 4. 請(qǐng)求完成(成功/失?。?,關(guān)閉 loading
      isLoading = false;
    });
}
  • 底層原因isLoading 是一個(gè)「狀態(tài)鎖」—— 支付請(qǐng)求發(fā)起前,先檢查鎖的狀態(tài):
    • 鎖為 true(請(qǐng)求中):直接返回,不發(fā)起新請(qǐng)求;
    • 鎖為 false(無(wú)請(qǐng)求):先上鎖,再發(fā)起請(qǐng)求,請(qǐng)求結(jié)束后解鎖。 這從「請(qǐng)求狀態(tài)」上阻斷了重復(fù)觸發(fā),無(wú)論節(jié)流窗口是否過(guò)期,只要請(qǐng)求沒(méi)完成,就無(wú)法再次執(zhí)行支付邏輯。

三、設(shè)計(jì)上的巧妙之處

節(jié)流 + loading 是“互補(bǔ)式”設(shè)計(jì),完美解決了單一方案的不足,核心巧妙點(diǎn)有 3 個(gè):

1. 分層防護(hù):前端“觸發(fā)層” + “執(zhí)行層”雙重?cái)r截

  • 節(jié)流屬于「觸發(fā)層防護(hù)」:攔截用戶“無(wú)意識(shí)的快速點(diǎn)擊”(比如手抖點(diǎn)了2次),是“前置攔截”,不進(jìn)入業(yè)務(wù)邏輯;
  • Loading 屬于「執(zhí)行層防護(hù)」:攔截用戶“有意識(shí)的重復(fù)點(diǎn)擊”(比如支付請(qǐng)求卡頓時(shí),用戶多次點(diǎn)擊),是“后置攔截”,即使節(jié)流失效(比如窗口過(guò)期),也能通過(guò)狀態(tài)鎖阻斷。 兩者結(jié)合,覆蓋了“快速點(diǎn)擊”和“請(qǐng)求中點(diǎn)擊”兩種最常見(jiàn)的重復(fù)支付場(chǎng)景。

2. 體驗(yàn)與安全兼顧

  • leading: true 保證用戶第一次點(diǎn)擊能立即響應(yīng),不會(huì)因?yàn)楣?jié)流導(dǎo)致“點(diǎn)擊沒(méi)反應(yīng)”的糟糕體驗(yàn);
  • trailing: false 避免“用戶點(diǎn)擊后等待一段時(shí)間,又莫名觸發(fā)支付”的問(wèn)題(比如用戶點(diǎn)擊后取消支付,結(jié)果2秒后又觸發(fā));
  • Loading 不僅是防重復(fù)的邏輯,還能給用戶視覺(jué)反饋(按鈕置灰、加載動(dòng)畫(huà)),讓用戶知道“系統(tǒng)正在處理”,減少重復(fù)點(diǎn)擊的沖動(dòng)。

3. 容錯(cuò)性強(qiáng):適配不同網(wǎng)絡(luò)/場(chǎng)景

  • 節(jié)流依賴“固定時(shí)間窗口”,但支付請(qǐng)求的耗時(shí)是不確定的(網(wǎng)絡(luò)快可能100ms完成,網(wǎng)絡(luò)慢可能5秒);
  • Loading 不依賴時(shí)間,只依賴“請(qǐng)求完成狀態(tài)”,無(wú)論請(qǐng)求耗時(shí)多久,只要沒(méi)完成就不會(huì)重復(fù)觸發(fā),完美適配不同網(wǎng)絡(luò)環(huán)境;
  • 即使節(jié)流函數(shù)出問(wèn)題(比如參數(shù)配置錯(cuò)誤、節(jié)流庫(kù)異常),Loading 仍能獨(dú)立起到防重復(fù)支付的核心作用,是“兜底保障”。

4. 2秒時(shí)長(zhǎng)的核心價(jià)值

我把節(jié)流時(shí)長(zhǎng)設(shè)為2秒,而非1秒或更短,本質(zhì)是給“異步loading”留足“兜底容錯(cuò)時(shí)間”,

1. 為什么短時(shí)長(zhǎng)(比如500ms)會(huì)有重復(fù)點(diǎn)擊風(fēng)險(xiǎn)?

POS機(jī)和普通瀏覽器不同,它的特點(diǎn)是:

  • 硬件性能弱:CPU/內(nèi)存有限,JS執(zhí)行、網(wǎng)絡(luò)請(qǐng)求的耗時(shí)會(huì)比普通設(shè)備長(zhǎng);
  • 異步操作延時(shí)大:goToPay里的loading是異步的(比如發(fā)起支付請(qǐng)求、更新DOM狀態(tài)),可能出現(xiàn)“節(jié)流窗口過(guò)期了,但loading還沒(méi)來(lái)得及關(guān)閉”的情況:
    時(shí)間線(假設(shè)節(jié)流設(shè)500ms):
    0ms → 用戶點(diǎn)擊,節(jié)流觸發(fā)goToPay,loading開(kāi)始異步開(kāi)啟(POS機(jī)卡,loading狀態(tài)還沒(méi)更新完成);
    500ms → 節(jié)流窗口過(guò)期,節(jié)流邏輯允許再次觸發(fā);
    600ms → 用戶再次點(diǎn)擊(以為沒(méi)反應(yīng)),此時(shí)loading還沒(méi)完全開(kāi)啟(isLoading還是false),直接觸發(fā)重復(fù)支付;
    

2. 2秒時(shí)長(zhǎng)的“兜底作用”

2秒是一個(gè)「足夠覆蓋POS機(jī)異步操作最大延時(shí)」的安全值:

  • 即使POS機(jī)性能差,loading的異步開(kāi)啟/關(guān)閉、支付請(qǐng)求的初始耗時(shí),也幾乎能在2秒內(nèi)完成;
  • 就算loading因?yàn)樵O(shè)備卡頓稍有延遲,2秒的節(jié)流窗口也能“撐到loading狀態(tài)生效”,避免“節(jié)流過(guò)期但loading沒(méi)鎖”的漏洞。

總結(jié)

核心關(guān)鍵點(diǎn)回顧:

  1. 雙重防護(hù)邏輯:節(jié)流控制「觸發(fā)頻率」(防快速點(diǎn)擊),loading 控制「請(qǐng)求狀態(tài)」(防請(qǐng)求中點(diǎn)擊),覆蓋所有重復(fù)支付場(chǎng)景;
  2. 巧妙的設(shè)計(jì)互補(bǔ):節(jié)流保證“點(diǎn)擊即時(shí)響應(yīng)”的體驗(yàn),loading 作為“兜底保障”適配不確定的請(qǐng)求耗時(shí);
  3. 體驗(yàn)與安全兼顧:loading 既是防重復(fù)的邏輯鎖,也是給用戶的視覺(jué)反饋,減少重復(fù)點(diǎn)擊的動(dòng)機(jī)。

節(jié)流防抖 傻傻分不清楚

一、先明確核心需求:支付場(chǎng)景的本質(zhì)要求

支付按鈕的核心訴求是:

  1. 用戶點(diǎn)擊后必須立即執(zhí)行支付邏輯(不能等、不能吞掉用戶的點(diǎn)擊);
  2. 短時(shí)間內(nèi)(比如2秒)多次點(diǎn)擊,只能執(zhí)行一次(防止重復(fù)支付);
  3. 2秒后再次點(diǎn)擊,仍能正常執(zhí)行(用戶第一次支付失敗,2秒后可以重新點(diǎn)擊)。

這三個(gè)訴求是判斷用節(jié)流還是防抖的關(guān)鍵,我們先對(duì)比兩者的核心差異:

特性節(jié)流 (throttle)防抖 (debounce)
核心邏輯「固定時(shí)間窗口內(nèi)只能執(zhí)行一次」,像水流一樣勻速通過(guò)「等待最后一次觸發(fā)后,延遲執(zhí)行」,像彈簧一樣松手才回彈
觸發(fā)時(shí)機(jī)窗口內(nèi)第一次觸發(fā)(leading: true)立即執(zhí)行只有停止觸發(fā)后,等待指定時(shí)間才執(zhí)行
多次觸發(fā)的結(jié)果窗口內(nèi)只執(zhí)行一次,窗口過(guò)期后可再次執(zhí)行只要一直在觸發(fā),就永遠(yuǎn)不執(zhí)行

二、為什么這里用節(jié)流,而不是防抖?

1. 防抖完全不符合支付場(chǎng)景的核心訴求

假設(shè)把代碼中的 throttle 換成 debounce,參數(shù)同樣設(shè)為2秒:

// 錯(cuò)誤示例:用防抖包裝支付函數(shù)
const debouncePay = debounce(() => {
  goToPay()
}, 2000);

會(huì)出現(xiàn)兩個(gè)致命問(wèn)題:

  • 問(wèn)題1:用戶正常點(diǎn)擊(只點(diǎn)1次)
    防抖會(huì)等待2秒后才執(zhí)行 goToPay —— 用戶點(diǎn)擊支付按鈕,看到頁(yè)面沒(méi)反應(yīng),會(huì)誤以為點(diǎn)擊失效,大概率會(huì)再次點(diǎn)擊,反而加劇重復(fù)點(diǎn)擊的問(wèn)題;
  • 問(wèn)題2:用戶連續(xù)點(diǎn)擊(點(diǎn)多次)
    防抖會(huì)“重置等待時(shí)間”—— 比如用戶1秒內(nèi)點(diǎn)了3次,防抖會(huì)從最后一次點(diǎn)擊開(kāi)始重新計(jì)時(shí)2秒,只要用戶不停點(diǎn)擊,支付邏輯就永遠(yuǎn)不會(huì)執(zhí)行,直接導(dǎo)致支付功能失效。

簡(jiǎn)單說(shuō):防抖的核心是「等用戶停手后再執(zhí)行」,而支付需要「用戶動(dòng)手就立即執(zhí)行,且短時(shí)間內(nèi)只執(zhí)行一次」,兩者的核心邏輯完全相悖。

2. 節(jié)流完美匹配支付場(chǎng)景的訴求

你代碼中的節(jié)流配置 { leading: true, trailing: false } 剛好命中支付需求:

  • leading: true:用戶第一次點(diǎn)擊時(shí),立即執(zhí)行支付邏輯(滿足“點(diǎn)擊必響應(yīng)”);
  • trailing: false:2秒窗口內(nèi)后續(xù)的點(diǎn)擊都被攔截(滿足“短時(shí)間內(nèi)只執(zhí)行一次”);
  • 2秒窗口過(guò)期后,再次點(diǎn)擊會(huì)重新觸發(fā)(滿足“失敗后可重新支付”)。

三、補(bǔ)充:什么時(shí)候才會(huì)用防抖?

防抖的適用場(chǎng)景是「需要等待用戶操作結(jié)束后再執(zhí)行」的場(chǎng)景,比如:

  1. 搜索框輸入聯(lián)想(等用戶輸完關(guān)鍵詞,再發(fā)請(qǐng)求查聯(lián)想詞,避免邊輸邊發(fā)請(qǐng)求);
  2. 窗口大小調(diào)整(等用戶拖完窗口,再執(zhí)行布局重繪,避免頻繁重繪);
  3. 手機(jī)號(hào)/驗(yàn)證碼輸入校驗(yàn)(等用戶輸完,再校驗(yàn)格式,避免邊輸邊提示錯(cuò)誤)。

這些場(chǎng)景的核心是“不著急執(zhí)行,等用戶停手再執(zhí)行”,和支付“必須立即執(zhí)行”的訴求完全相反。

總結(jié)

核心關(guān)鍵點(diǎn)回顧:

  1. 核心邏輯差異:節(jié)流是「固定時(shí)間內(nèi)只執(zhí)行一次」,保證觸發(fā)即響應(yīng);防抖是「等最后一次觸發(fā)后延遲執(zhí)行」,會(huì)吞掉中間的觸發(fā);
  2. 場(chǎng)景匹配度:支付需要“點(diǎn)擊立即執(zhí)行+短時(shí)間防重復(fù)”,節(jié)流剛好滿足,防抖會(huì)導(dǎo)致“點(diǎn)擊不立即響應(yīng)”甚至“永遠(yuǎn)不執(zhí)行”;
  3. 記憶技巧:節(jié)流=“控制頻率”(多久執(zhí)行一次),防抖=“等待結(jié)束”(停手才執(zhí)行),支付場(chǎng)景要“控頻率”而非“等結(jié)束”。

防抖/節(jié)流 設(shè)計(jì)的巧妙之處

一、核心設(shè)計(jì)巧思:用「狀態(tài)管理」馴服高頻觸發(fā)

防抖和節(jié)流的本質(zhì),是通過(guò)管理“唯一狀態(tài)” 把「無(wú)規(guī)律的高頻觸發(fā)」轉(zhuǎn)化為「可控的低頻執(zhí)行」,這是最核心的巧妙之處:

1. 防抖:用「定時(shí)器狀態(tài)」實(shí)現(xiàn)“等待最后一次”

  • 問(wèn)題本質(zhì):高頻觸發(fā)(比如搜索框輸入)如果每次都執(zhí)行,會(huì)頻繁發(fā)請(qǐng)求/渲染,浪費(fèi)性能;
  • 巧妙設(shè)計(jì):只維護(hù)一個(gè)「定時(shí)器ID」?fàn)顟B(tài),每次觸發(fā)時(shí):
    • 先清除舊定時(shí)器(重置等待時(shí)間)—— 相當(dāng)于“電梯每次按關(guān)門(mén)鍵都重新等”;
    • 再創(chuàng)建新定時(shí)器(設(shè)定新的等待時(shí)間)—— 只有最后一次觸發(fā)后,定時(shí)器沒(méi)被清除,才會(huì)執(zhí)行;
  • 為什么妙:用「一個(gè)變量+兩個(gè)操作(清/設(shè)定時(shí)器)」就實(shí)現(xiàn)了“等待用戶操作結(jié)束”的核心訴求,沒(méi)有多余邏輯,狀態(tài)管理極簡(jiǎn)。

2. 節(jié)流:用「開(kāi)關(guān)/時(shí)間戳狀態(tài)」實(shí)現(xiàn)“頻率控制”

  • 問(wèn)題本質(zhì):高頻觸發(fā)(比如滾動(dòng)/點(diǎn)擊)需要保證“每隔固定時(shí)間只執(zhí)行一次”,既不浪費(fèi)性能,又能及時(shí)響應(yīng);
  • 巧妙設(shè)計(jì):只維護(hù)一個(gè)「開(kāi)關(guān)(canRun)」或「時(shí)間戳(lastTime)」?fàn)顟B(tài),每次觸發(fā)時(shí):
    • 先判斷狀態(tài)(開(kāi)關(guān)是否關(guān)閉/時(shí)間差是否夠)—— 相當(dāng)于“閘機(jī)先看是否在冷卻期”;
    • 只有狀態(tài)滿足(開(kāi)關(guān)開(kāi)/時(shí)間差夠),才執(zhí)行目標(biāo)函數(shù),并更新?tīng)顟B(tài)(關(guān)開(kāi)關(guān)/更新時(shí)間戳);
    • 冷卻期結(jié)束后,自動(dòng)恢復(fù)狀態(tài)(開(kāi)開(kāi)關(guān));
  • 為什么妙:用「一個(gè)布爾值/數(shù)字」就精準(zhǔn)控制了執(zhí)行頻率,沒(méi)有復(fù)雜的計(jì)數(shù)/隊(duì)列,邏輯極簡(jiǎn)且性能開(kāi)銷幾乎為0。

二、場(chǎng)景適配巧思:既解決技術(shù)問(wèn)題,又貼合「用戶行為」

防抖和節(jié)流的設(shè)計(jì)不只是“技術(shù)層面的優(yōu)化”,更精準(zhǔn)適配了「人類操作的特點(diǎn)」,這是容易被忽略的巧妙之處:

1. 防抖:貼合“用戶需要完成操作后再反饋”的行為

  • 比如搜索框輸入:用戶不會(huì)只輸一個(gè)字就等結(jié)果,而是輸完一整句話才需要聯(lián)想;
  • 防抖的“等待最后一次觸發(fā)”剛好貼合這個(gè)行為—— 既避免了“邊輸邊發(fā)請(qǐng)求”的性能浪費(fèi),又保證“用戶輸完就出結(jié)果”的體驗(yàn);
  • 對(duì)比笨辦法(比如每輸入一個(gè)字都發(fā)請(qǐng)求):防抖既不犧牲體驗(yàn),又能減少90%以上的無(wú)效請(qǐng)求。

2. 節(jié)流:貼合“用戶需要即時(shí)反饋,但不能太頻繁”的行為

  • 比如支付按鈕點(diǎn)擊:用戶點(diǎn)擊后需要“立即響應(yīng)”(不能等),但又要防止“手抖點(diǎn)多次”;
  • 節(jié)流的“冷卻期控制”剛好貼合這個(gè)行為—— 第一次點(diǎn)擊立即執(zhí)行(滿足“即時(shí)反饋”),冷卻期內(nèi)攔截重復(fù)點(diǎn)擊(滿足“防重復(fù)”),冷卻期后可重新執(zhí)行(滿足“失敗后重試”);
  • 對(duì)比笨辦法(比如點(diǎn)擊后禁用按鈕):節(jié)流不用修改DOM狀態(tài),只是“邏輯層面的頻率控制”,更通用(可復(fù)用在滾動(dòng)、resize等無(wú)DOM的場(chǎng)景)。

3. 可配置化擴(kuò)展:兼顧“通用性”和“個(gè)性化”

優(yōu)秀的防抖/節(jié)流實(shí)現(xiàn)(比如lodash版)還會(huì)設(shè)計(jì)leading(是否立即執(zhí)行)、trailing(是否延遲執(zhí)行)等參數(shù),比如:

  • 防抖加immediate: true:可適配“第一次觸發(fā)立即執(zhí)行,后續(xù)觸發(fā)重置”的場(chǎng)景(比如彈窗關(guān)閉按鈕);
  • 節(jié)流加leading: false, trailing: true:可適配“滾動(dòng)結(jié)束后再執(zhí)行”的場(chǎng)景;
  • 為什么妙:基礎(chǔ)邏輯不變,通過(guò)簡(jiǎn)單參數(shù)配置就能適配不同場(chǎng)景,做到“一次編寫(xiě),多處復(fù)用”,符合「開(kāi)閉原則」(對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉)。

三、實(shí)現(xiàn)細(xì)節(jié)巧思:最小侵入性+無(wú)副作用

防抖/節(jié)流的設(shè)計(jì)還藏著很多“細(xì)節(jié)上的巧思”,保證了函數(shù)的健壯性和易用性:

1. 保留this和參數(shù):無(wú)副作用封裝

// 核心代碼片段
return function(...args) {
  fn.apply(this, args); // 關(guān)鍵:綁定原函數(shù)的this和參數(shù)
};
  • 為什么妙:如果直接調(diào)用fn(),會(huì)丟失原函數(shù)的this(比如DOM事件中的this指向元素)和參數(shù)(比如事件對(duì)象e);
  • apply(this, args)保留上下文和參數(shù),讓防抖/節(jié)流函數(shù)“透明”包裹目標(biāo)函數(shù),原函數(shù)的行為完全不變—— 這是“無(wú)副作用封裝”的關(guān)鍵,也是能通用的核心。

2. 支持取消:應(yīng)對(duì)極端場(chǎng)景

// 防抖擴(kuò)展:添加取消功能
function debounce(fn, delay) {
  let timer = null;
  const debounced = function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
  // 新增取消方法
  debounced.cancel = function() {
    clearTimeout(timer);
    timer = null;
  };
  return debounced;
}
  • 為什么妙:比如用戶輸入一半突然不想搜了,點(diǎn)擊取消按鈕,可通過(guò)debounced.cancel()清除定時(shí)器,避免“已經(jīng)取消操作但還執(zhí)行函數(shù)”的問(wèn)題;
  • 這種設(shè)計(jì)讓函數(shù)更健壯,能應(yīng)對(duì)“中途終止”的極端場(chǎng)景。

3. 無(wú)全局污染:狀態(tài)私有化

  • 防抖/節(jié)流的核心狀態(tài)(定時(shí)器ID、開(kāi)關(guān)、時(shí)間戳)都定義在「閉包」中,而不是全局變量;
  • 為什么妙:每個(gè)被防抖/節(jié)流包裝的函數(shù),都有自己獨(dú)立的狀態(tài),不會(huì)互相干擾(比如同時(shí)包裝兩個(gè)按鈕的點(diǎn)擊事件,各自的冷卻期互不影響);
  • 對(duì)比用全局變量存狀態(tài):閉包讓狀態(tài)私有化,避免了“全局變量沖突”的問(wèn)題,符合「模塊化」設(shè)計(jì)思想。

總結(jié)

防抖/節(jié)流設(shè)計(jì)的核心巧妙點(diǎn)回顧:

  1. 極簡(jiǎn)狀態(tài)管理:只用一個(gè)核心狀態(tài)(定時(shí)器/開(kāi)關(guān)/時(shí)間戳),就馴服了高頻觸發(fā),邏輯簡(jiǎn)單且性能開(kāi)銷極低;
  2. 貼合用戶行為:不是單純的技術(shù)優(yōu)化,而是精準(zhǔn)適配人類操作特點(diǎn)(防抖等結(jié)束、節(jié)流控頻率),兼顧性能和體驗(yàn);
  3. 無(wú)侵入性封裝:保留原函數(shù)的this和參數(shù),狀態(tài)私有化,支持?jǐn)U展(取消、配置參數(shù)),做到“通用、無(wú)副作用、可擴(kuò)展”。

防抖/節(jié)流 實(shí)現(xiàn)如何快速記住

記「極簡(jiǎn)固定模板」(只記核心結(jié)構(gòu),不用記細(xì)節(jié))

我?guī)湍闾釤捔?ldquo;萬(wàn)能模板”,核心代碼只有幾行,記模板比記零散代碼容易10倍:

1. 防抖(debounce)模板(核心:重置定時(shí)器)

模板邏輯

  • 初始化一個(gè)定時(shí)器變量(存定時(shí)器ID);
  • 每次觸發(fā)函數(shù)時(shí),先清掉舊定時(shí)器(重置等待時(shí)間);
  • 再設(shè)新定時(shí)器,延遲后執(zhí)行目標(biāo)函數(shù)。

手寫(xiě)代碼(帶注釋,只記標(biāo)★的核心行)

// 防抖函數(shù):fn=目標(biāo)函數(shù),delay=延遲時(shí)間
function debounce(fn, delay) {
  let timer = null; // ★ 唯一狀態(tài):定時(shí)器(電梯的“等待倒計(jì)時(shí)”)
  
  // 返回包裝后的函數(shù)(用戶每次點(diǎn)擊/觸發(fā)都會(huì)執(zhí)行這個(gè)函數(shù))
  return function(...args) {
    // ★ 核心1:觸發(fā)時(shí)先清舊定時(shí)器(按關(guān)門(mén)鍵,重置2秒等待)
    clearTimeout(timer);
    
    // ★ 核心2:設(shè)新定時(shí)器,延遲后執(zhí)行目標(biāo)函數(shù)(等2秒,沒(méi)人按就關(guān)門(mén))
    timer = setTimeout(() => {
      fn.apply(this, args); // 保留this和參數(shù)(適配實(shí)際場(chǎng)景)
    }, delay);
  };
}

簡(jiǎn)化記憶:防抖=「清舊定時(shí)器→設(shè)新定時(shí)器」,就這兩步核心,其他都是適配性代碼(apply是為了綁定this,可后期補(bǔ))。

2. 節(jié)流(throttle)模板(核心:判斷冷卻期)

模板邏輯

  • 初始化一個(gè)“是否可執(zhí)行”的開(kāi)關(guān)(或記錄上次執(zhí)行時(shí)間);
  • 觸發(fā)時(shí)先判斷:如果在冷卻期(開(kāi)關(guān)關(guān)/時(shí)間沒(méi)到),直接返回;
  • 如果不在冷卻期:先關(guān)掉開(kāi)關(guān)(進(jìn)入冷卻),執(zhí)行目標(biāo)函數(shù),延遲后打開(kāi)開(kāi)關(guān)(結(jié)束冷卻)。

手寫(xiě)代碼(兩種常見(jiàn)寫(xiě)法,記一種就行,推薦第一種)

// 節(jié)流函數(shù):fn=目標(biāo)函數(shù),delay=冷卻時(shí)間
function throttle(fn, delay) {
  let canRun = true; // ★ 唯一狀態(tài):冷卻開(kāi)關(guān)(閘機(jī)的“是否可用”)
  
  return function(...args) {
    // ★ 核心1:冷卻期內(nèi),直接返回(閘機(jī)不可用,刷了也白刷)
    if (!canRun) return;
    
    // ★ 核心2:關(guān)閉開(kāi)關(guān),進(jìn)入冷卻(閘機(jī)用一次,鎖2秒)
    canRun = false;
    // 執(zhí)行目標(biāo)函數(shù)(閘機(jī)開(kāi)門(mén))
    fn.apply(this, args);
    
    // ★ 核心3:延遲后打開(kāi)開(kāi)關(guān)(2秒后閘機(jī)恢復(fù)可用)
    setTimeout(() => {
      canRun = true;
    }, delay);
  };
}

簡(jiǎn)化記憶:節(jié)流=「判斷開(kāi)關(guān)→關(guān)開(kāi)關(guān)→執(zhí)行→延遲開(kāi)開(kāi)關(guān)」,核心是“開(kāi)關(guān)控制冷卻期”。

補(bǔ)充:節(jié)流的另一種寫(xiě)法(按時(shí)間戳,邏輯一致)

如果面試官讓用時(shí)間戳寫(xiě),只是“冷卻期判斷方式”變了,核心還是“控冷卻”:

function throttle(fn, delay) {
  let lastTime = 0; // 上次執(zhí)行時(shí)間(替代canRun)
  
  return function(...args) {
    const now = Date.now();
    // 核心:判斷當(dāng)前時(shí)間 - 上次執(zhí)行時(shí)間 ≥ 延遲時(shí)間(冷卻期過(guò)了)
    if (now - lastTime >= delay) {
      fn.apply(this, args);
      lastTime = now; // 更新上次執(zhí)行時(shí)間(關(guān)開(kāi)關(guān))
    }
  };
}

記憶:時(shí)間戳寫(xiě)法只是把“開(kāi)關(guān)”換成了“時(shí)間差判斷”,核心還是“冷卻期內(nèi)不執(zhí)行”。

四、第三步:記「唯一差異點(diǎn)」(避免混淆)

防抖和節(jié)流的代碼就一個(gè)核心區(qū)別,記死這一點(diǎn),永遠(yuǎn)不會(huì)混:

對(duì)比項(xiàng)防抖 (debounce)節(jié)流 (throttle)
核心操作每次觸發(fā)都「清定時(shí)器」(重置)觸發(fā)時(shí)先「判斷冷卻期」(攔截)
定時(shí)器作用延遲執(zhí)行目標(biāo)函數(shù)延遲結(jié)束冷卻期
執(zhí)行時(shí)機(jī)停止觸發(fā)后延遲執(zhí)行觸發(fā)時(shí)立即執(zhí)行(冷卻期過(guò)的話)

一句話總結(jié)差異

  • 防抖:「先清后設(shè)」定時(shí)器(清舊的,設(shè)新的);
  • 節(jié)流:「先判斷后執(zhí)行」(判斷能不能執(zhí)行,能就執(zhí)行+鎖冷卻)。

五、記憶技巧:3分鐘快速默寫(xiě)訓(xùn)練(每天練1次,3天就記?。?/h3>

不用死背,按這個(gè)步驟練,每次只花3分鐘:

  1. 第一步(1分鐘):先默念場(chǎng)景→防抖=電梯、節(jié)流=閘機(jī);
  2. 第二步(1分鐘):寫(xiě)核心模板(只寫(xiě)標(biāo)★的行):
    • 防抖:let timer → clearTimeout(timer) → timer = setTimeout(...);
    • 節(jié)流:let canRun=true → if(!canRun)return → canRun=false → setTimeout(開(kāi)canRun);
  3. 第三步(1分鐘):補(bǔ)全適配代碼(apply(this, args))。

練3次后,你會(huì)發(fā)現(xiàn):不用記完整代碼,只要寫(xiě)出核心邏輯,剩下的都是“填空”。

六、完整可運(yùn)行代碼(對(duì)照練)

最后給你完整的防抖+節(jié)流代碼,練的時(shí)候?qū)φ眨?/p>

// 1. 防抖函數(shù)(帶立即執(zhí)行可選參數(shù),進(jìn)階版,先記基礎(chǔ)版,再補(bǔ)這個(gè))
function debounce(fn, delay, immediate = false) {
  let timer = null;
  return function(...args) {
    // 每次觸發(fā)清舊定時(shí)器
    clearTimeout(timer);
    
    // 立即執(zhí)行版(可選,基礎(chǔ)版不用記這個(gè))
    if (immediate && !timer) {
      fn.apply(this, args);
    }
    
    // 設(shè)新定時(shí)器
    timer = setTimeout(() => {
      fn.apply(this, args);
      timer = null; // 重置timer,方便immediate判斷
    }, delay);
  };
}

// 2. 節(jié)流函數(shù)(開(kāi)關(guān)版,最易記)
function throttle(fn, delay) {
  let canRun = true;
  return function(...args) {
    if (!canRun) return;
    canRun = false;
    fn.apply(this, args);
    setTimeout(() => {
      canRun = true;
    }, delay);
  };
}

// 測(cè)試用例(練完可以跑一下,加深印象)
// 防抖測(cè)試:連續(xù)點(diǎn)擊,只在最后一次點(diǎn)擊后1秒執(zhí)行
const debounceClick = debounce(() => console.log('防抖執(zhí)行'), 1000);
// 節(jié)流測(cè)試:連續(xù)點(diǎn)擊,每1秒執(zhí)行一次
const throttleClick = throttle(() => console.log('節(jié)流執(zhí)行'), 1000);

總結(jié)

核心關(guān)鍵點(diǎn)回顧:

  1. 記錨點(diǎn):防抖=搜索框輸入(重置等待),節(jié)流=閘機(jī)(冷卻期),先想場(chǎng)景再想代碼;
  2. 記模板:防抖核心是「清舊定時(shí)器→設(shè)新定時(shí)器」,節(jié)流核心是「判斷開(kāi)關(guān)→關(guān)開(kāi)關(guān)→延遲開(kāi)開(kāi)關(guān)」;
  3. 記差異:防抖是“重置時(shí)間”,節(jié)流是“控制頻率”,核心操作一個(gè)清定時(shí)器、一個(gè)判斷冷卻期。

到此這篇關(guān)于前端防止重復(fù)支付解決方案的文章就介紹到這了,更多相關(guān)前端防止重復(fù)支付內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • JavaScript如何正確聲明和使用變量詳細(xì)代碼實(shí)例

    JavaScript如何正確聲明和使用變量詳細(xì)代碼實(shí)例

    變量是程序在內(nèi)存中申請(qǐng)的一塊用來(lái)存放數(shù)據(jù)的空間,下面這篇文章主要給大家介紹了關(guān)于JavaScript如何正確聲明和使用變量的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-07-07
  • JS庫(kù)之wow.js使用方法

    JS庫(kù)之wow.js使用方法

    近日,在做項(xiàng)目中,需要做到滾動(dòng)條滑到某個(gè)位置時(shí),才能顯示動(dòng)畫(huà),網(wǎng)上查詢到有個(gè)wow.js可以達(dá)到要求,現(xiàn)在把使用方法做如下總結(jié),需要的朋友參考下吧
    2017-09-09
  • js添加千分位的實(shí)現(xiàn)代碼(超簡(jiǎn)單)

    js添加千分位的實(shí)現(xiàn)代碼(超簡(jiǎn)單)

    下面小編就為大家?guī)?lái)一篇js添加千分位的實(shí)現(xiàn)代碼(超簡(jiǎn)單)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2016-08-08
  • Bootstrap彈出框之自定義懸??驑?biāo)題、內(nèi)容和樣式示例代碼

    Bootstrap彈出框之自定義懸??驑?biāo)題、內(nèi)容和樣式示例代碼

    這篇文章主要介紹了Bootstrap彈出框之自定義懸??驑?biāo)題、內(nèi)容和樣式示例代碼,需要的朋友可以參考下
    2017-07-07
  • TypeScript中infer關(guān)鍵字的具體使用

    TypeScript中infer關(guān)鍵字的具體使用

    infer 是 TypeScript 中的高級(jí)類型關(guān)鍵字,主要用于條件類型中推斷類型,本文就來(lái)介紹TypeScript中infer關(guān)鍵字的使用,具有一定的參考價(jià)值,感興趣的可以了解一下
    2025-06-06
  • uniapp獲取頁(yè)面高度與元素高度簡(jiǎn)單示例

    uniapp獲取頁(yè)面高度與元素高度簡(jiǎn)單示例

    在實(shí)際開(kāi)發(fā)中我們會(huì)遇到不確定高度的情況,那么在uniapp中我們?nèi)绾潍@取區(qū)域的高度吶?這篇文章主要給大家介紹了關(guān)于uniapp獲取頁(yè)面高度與元素高度的相關(guān)資料,需要的朋友可以參考下
    2023-09-09
  • webpack的pitching loader詳解

    webpack的pitching loader詳解

    這篇文章主要介紹了webpack的pitching loader詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-09-09
  • js 有框架頁(yè)面跳轉(zhuǎn)(target)三種情況下的應(yīng)用

    js 有框架頁(yè)面跳轉(zhuǎn)(target)三種情況下的應(yīng)用

    本文主要介紹下有框架頁(yè)面的跳轉(zhuǎn):跳出框架,在父頁(yè)面;從一個(gè)框架跳轉(zhuǎn)到 name=main的框架里;ContentList 當(dāng)前頁(yè)的iframe名字,感興趣的朋友可以了解下哈
    2013-04-04
  • JavaScript實(shí)現(xiàn)窗口抖動(dòng)效果

    JavaScript實(shí)現(xiàn)窗口抖動(dòng)效果

    抖動(dòng)效果在各大網(wǎng)頁(yè)上都常遇到,這篇文章主要介紹了JavaScript實(shí)現(xiàn)窗口抖動(dòng)效果的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下
    2016-10-10
  • 使用webpack搭建vue項(xiàng)目實(shí)現(xiàn)腳手架功能

    使用webpack搭建vue項(xiàng)目實(shí)現(xiàn)腳手架功能

    這篇文章主要介紹了使用webpack搭建vue項(xiàng)目實(shí)現(xiàn)腳手架功能,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-03-03

最新評(píng)論

兴隆县| 桑日县| 英吉沙县| 称多县| 定州市| 大邑县| 松滋市| 漳平市| 南木林县| 孟津县| 澎湖县| 龙岩市| 勐海县| 鸡东县| 安仁县| 正宁县| 岳西县| 紫金县| 遵义市| 澎湖县| 内丘县| 黄山市| 原阳县| 韩城市| 温泉县| 锦州市| 太和县| 长兴县| 通州市| 宝山区| 象山县| 调兵山市| 罗江县| 咸宁市| 兰考县| 于都县| 诏安县| 永春县| 邳州市| 宝坻区| 临夏县|