🌷 古之立大事者,不惟有超世之才,亦必有堅忍不拔之志
🎐 個人CSND主頁——Micro麥可樂的博客
🐥《Docker實操教程》專欄以最新的Centos版本為基礎進行Docker實操教程,入門到實戰
🌺《RabbitMQ》專欄19年編寫主要介紹使用JAVA開發RabbitMQ的系列教程,從基礎知識到項目實戰
🌸《設計模式》專欄以實際的生活場景為案例進行講解,讓大家對設計模式有一個更清晰的理解
🌛《開源項目》本專欄主要介紹目前熱門的開源項目,帶大家快速了解并輕松上手使用
?《開發技巧》本專欄包含了各種系統的設計原理以及注意事項,并分享一些日常開發的功能小技巧
💕《Jenkins實戰》專欄主要介紹Jenkins+Docker的實戰教程,讓你快速掌握項目CI/CD,是2024年最新的實戰教程
🌞《Spring Boot》專欄主要介紹我們日常工作項目中經常應用到的功能以及技巧,代碼樣例完整
🌞《Spring Security》專欄中我們將逐步深入Spring Security的各個技術細節,帶你從入門到精通,全面掌握這一安全技術
如果文章能夠給大家帶來一定的幫助!歡迎關注、評論互動~
前端函數防抖(Debounce)完整講解 - 從原理、應用到完整實現
- 1. 前言
- 2. 為什么使用防抖?
- 3. 函數防抖的應用場景
- 4. 完整代碼實現
- ? 基礎防抖函數
- ? 使用示例(輸入框搜索)
- ? React Hooks 防抖實現
- ? 使用 Lodash 的 _.debounce
- 5. 高階技巧與注意事項
- 6. 結語
1. 前言
在我們日常前端開發中,高頻觸發的事件(如輸入框輸入、窗口縮放、滾動事件)可能導致性能問題甚至引發BUG。函數防抖(debounce)
是一種常見且高效的性能優化手段,用于限制高頻事件觸發下的函數調用次數,從而減少不必要的計算、網絡請求或 DOM 操作。
本文博主將帶著小伙伴一起深入解析防抖機制的原理、六大應用場景,并提供可直接復用的代碼示例。
2. 為什么使用防抖?
我們來看看你是否也遇到這樣的問題:
當用戶快速連續觸發事件時,例如:
- 搜索框每輸入一個字符立即請求接口
- 窗口縮放時頻繁更新布局
- 瘋狂點擊提交按鈕
會導致:
- 性能浪費:不必要的計算/請求
- 數據錯亂:異步請求響應順序不可控
- 用戶體驗差:界面卡頓或閃爍
函數防抖的核心思想是在連續觸發的事件停止后,僅執行最后一次調用,以避免頻繁觸發帶來的性能問題。
延遲執行 + 重置計時器:在事件被觸發后等待指定時間(如300ms),若期間沒有再次觸發,則執行函數;若期間重復觸發,則重新開始計時
3. 函數防抖的應用場景
場景 | 示例 | 解決方案 |
---|---|---|
搜索建議 | 輸入框聯想詞查詢 | 停止輸入300ms后發起請求 |
按鈕提交 | 防止重復提交訂單 | 點擊后禁用按鈕直至操作完成 |
窗口調整 | 響應式布局計算 | 窗口停止調整后執行計算 |
滾動加載 | 無限滾動加載更多 | 停止滾動后觸發檢測 |
畫布繪制 | 實時預覽圖形渲染 | 停止拖拽后更新渲染 |
表單驗證 | 密碼強度實時檢測 | 輸入結束再進行復雜校驗 |
為了讓大家更清晰了解其應用場景,我們例舉幾個說明:
1、輸入框實時搜索
在用戶輸入關鍵詞時觸發搜索接口,若不加限制,每次 keyup 都會發起請求,極易導致接口壓力過大。使用防抖后,只在用戶停止輸入(如 300ms)后才發送請求,有效降低調用次數
2、按鈕防連點
對于提交表單或支付按鈕,連續點擊可能導致多次提交。給點擊事件綁定防抖函數,可在用戶短時間內多次點擊時只執行一次提交操作
3、窗口大小調整(resize)
當頁面布局需根據窗口大小實時計算或重繪時,resize 事件會頻繁觸發,添加防抖能減少重繪次數,提升性能
4、滾動監聽
結合無限滾動或懶加載,當用戶滾動頁面時應控制數據加載頻率,避免重復請求或過度渲染
4. 完整代碼實現
防抖函數
通過內部維護一個定時器 ID,每次調用時先清除之前的定時器,再啟動一個新的延遲執行定時器;只有在最后一次調用后的延遲時間到達后,才真正執行目標函數
? 基礎防抖函數
先看一個簡單實現:
/*** 防抖函數* @param {Function} fn 需要防抖的函數* @param {number} delay 延遲時間(毫秒)* @returns {Function} 包裝后的防抖函數*/
function debounce(fn, delay = 300) {let timer = nullreturn function(...args) {// 每次觸發時清除之前的計時器if (timer) clearTimeout(timer)// 設置新的計時器timer = setTimeout(() => {fn.apply(this, args) // 確保正確的this上下文timer = null}, delay)}
}
上述代碼利用 JavaScript 閉包,讓每個防抖函數維護獨立的 timeoutId,在多次調用時只有最后一次延遲結束后觸發
? 使用示例(輸入框搜索)
<input type="text" id="searchInput"><script>
const searchInput = document.getElementById('searchInput')// 原始請求函數
function fetchSearchResult(keyword) {console.log(`搜索關鍵詞: ${keyword}`)// 實際調用API接口...
}// 包裝為防抖版本(500ms延遲)
const debouncedFetch = debounce(fetchSearchResult, 500)// 綁定輸入事件
searchInput.addEventListener('input', (e) => {debouncedFetch(e.target.value.trim())
})
</script>
? React Hooks 防抖實現
import { useCallback, useEffect, useRef } from 'react'// 自定義防抖Hook
function useDebounce(fn, delay) {const timerRef = useRef(null)const debouncedFn = useCallback((...args) => {if (timerRef.current) clearTimeout(timerRef.current)timerRef.current = setTimeout(() => {fn(...args)timerRef.current = null}, delay)}, [fn, delay])// 組件卸載時清除計時器useEffect(() => {return () => {if (timerRef.current) clearTimeout(timerRef.current)}}, [])return debouncedFn
}// 在組件中使用
function SearchBox() {const [keyword, setKeyword] = useState('')// 防抖請求函數const debouncedSearch = useDebounce((value) => {console.log('實際搜索:', value)}, 500)const handleChange = (e) => {setKeyword(e.target.value)debouncedSearch(e.target.value)}return <input value={keyword} onChange={handleChange} />
}
? 使用 Lodash 的 _.debounce
在實際項目中,為了減少手寫錯誤并獲得更豐富的功能(如 leading、trailing、cancel、flush
等選項),推薦使用成熟的工具庫 Lodash
的 _.debounce
方法
# 安裝 lodash.debounce 子模塊
npm install lodash.debounce
快速使用:
import debounce from 'lodash.debounce';/** * 在搜索框中使用防抖 * 當用戶停止輸入 300ms 后才觸發搜索 */
const searchInput = document.getElementById('search');
function onSearch(query) {// 發送搜索請求console.log('搜索關鍵詞:', query);
}
const debouncedSearch = debounce(onSearch, 300, { leading: false, trailing: true });searchInput.addEventListener('input', (e) => {debouncedSearch(e.target.value);
});
參數說明
- leading: 是否在延遲開始前調用一次,默認 false。
- trailing: 是否在延遲結束后調用一次,默認 true。
- 返回的函數還擁有 cancel() 和 flush() 方法,可在需要時取消或立即執行待定調用
5. 高階技巧與注意事項
-
立即執行模式:
在不使用Lodash
庫時,我們也可以添加立即執行選項,首次觸發立即執行,后續觸發進入防抖function debounce(fn, delay, immediate = false) {let timerreturn function(...args) {if (immediate && !timer) fn.apply(this, args)if (timer) clearTimeout(timer)timer = setTimeout(() => {if (!immediate) fn.apply(this, args)timer = null}, delay)} }
-
防抖與節流區別:
- 防抖(
Debounce
):等電梯(最后一個人進來后等10秒關門) - 節流(
Throttle
):發短信(每60秒只能發一次)
- 防抖(
-
性能優化:
- 高頻事件(如mousemove)建議結合
requestAnimationFrame
- 避免在防抖函數中處理大型對象
- 高頻事件(如mousemove)建議結合
6. 結語
函數防抖是前端性能優化中的一項基礎技術,適用于各種需要限制高頻事件調用的場景,通過本文介紹的 定時器邏輯
或成熟的 Lodash 工具庫
,就能快速落地。通過本文的代碼示例,小伙伴們可以快速將其應用到實際項目中。當遇到高頻觸發事件時,不妨先思考:這個場景是否需要防抖?
如果你在實踐過程中有任何疑問或更好的擴展思路,歡迎在評論區留言,最后希望大家 一鍵三連 給博主一點點鼓勵!
前端技術專欄回顧:
01【前端技術】 ES6 介紹及常用語法說明
02【前端技術】標簽頁通訊localStorage、BroadcastChannel、SharedWorker的技術詳解
03 前端請求亂序問題分析與AbortController、async/await、Promise.all等解決方案
04 前端開發中深拷貝的循環引用問題:從問題復現到完美解決
05 前端AJAX請求上傳下載進度監控指南詳解與完整代碼示例
06 TypeScript 進階指南 - 使用泛型與keyof約束參數
07 前端實現視頻文件動畫幀圖片提取全攻略 - 附完整代碼樣例