在前端開發中,我們經常需要處理一些高頻觸發的事件,比如:
- 輸入框搜索建議(
input
或 keyup
) - 窗口調整大?。?code style="font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 0.87em; word-break: break-word; border-radius: 2px; overflow-x: auto; background-color: rgb(255, 245, 245); color: rgb(255, 80, 44); padding: 0.065em 0.4em;">resize)
- 滾動事件(
scroll
) - 鼠標移動(
mousemove
)
這些事件如果每次都執行某些代價較高的操作(如發起網絡請求、重排重繪頁面等),會對性能造成嚴重影響。為了解決這個問題,我們可以使用 防抖(debounce) 和 節流(throttle) 技術。
?? 一、什么是防抖(Debounce)?
定義:
防抖 是指,在一段時間內多次觸發同一個函數,只有最后一次觸發后經過指定時間沒有再次觸發,才會真正執行該函數。
類比:
就像你在打字時,搜索引擎不會每次按鍵都去請求服務器,而是等你停下來后再請求。 就像你打游戲的回城,自己移動了或被打斷了就只能重新回城,直到停下來等了指定時間才會真正回城成功。
應用場景:
- 搜索框輸入實時建議
- 表單驗證
- 窗口調整尺寸
- 多次點擊按鈕(避免重復提交)
? 示例代碼:
function debounce(fn, delay) {
return function (args) {
const that = this;
clearTimeout(fn.id);
fn.id = setTimeout(() => {
fn.call(that, args);
}, delay);
};
}
?? 使用示例:
const inputA = document.getElementById('inputA');
inputA.addEventListener('keyup', debounce(function(e) {
console.log('發送請求:', e.target.value);
}, 300));
?? 二、什么是節流(Throttle)?
定義:
節流 是指,在一定時間間隔內只允許執行一次函數。無論在這段時間內觸發多少次,函數只會執行一次。
類比:
就像游戲中的技能冷卻,每 5 秒只能釋放一次技能。
應用場景:
- 頁面滾動加載更多內容(如瀑布流)
- 窗口調整大?。ū苊忸l繁布局計算)
- 實時位置更新(如地圖定位)
? 示例代碼:
function throttle(fn, delay) {
let last = 0;
return function (...args) {
const now = +new Date();
if (!last || now - last >= delay) {
fn.apply(this, args);
last = now;
}
};
}
?? 使用示例:
window.addEventListener('resize', throttle(function() {
console.log('窗口大小變化');
}, 200));
?? 三、防抖 vs 節流:對比總結
特性 | 防抖(Debounce) | 節流(Throttle) |
---|
原理 | 在規定時間內未被再次觸發才執行 | 固定時間只執行一次 |
觸發頻率 | 多次觸發 → 只執行最后一次 | 多次觸發 → 每隔一段時間執行一次 |
典型用途 | 搜索建議、表單校驗 | 滾動監聽、窗口調整、動畫幀控制 |
?? 四、進階技巧與常見問題
1. 如何保證 this
不丟失?
在對象方法或事件回調中,this
很容易指向全局對象(如 window
)。解決方案有:
? 方法一:用變量保存 this
obj.inc = debounce(function(val) {
const that = this;
setTimeout(function () {
that.count += val;
}, 500);
});
? 方法二:使用 .bind()
fn.bind(that)(args);
? 方法三:使用箭頭函數(推薦)
setTimeout(() => {
fn(args);
}, delay);
箭頭函數不綁定自己的 this
,繼承外層函數的 this
,非常適用于事件處理和定時器。
2. 如何兼容箭頭函數和普通函數?
如果你傳入的是一個箭頭函數作為 fn
,也要注意它的 this
綁定行為。通常我們會優先使用箭頭函數來簡化上下文管理。
3. 如何封裝成可復用的工具函數?
你可以將防抖和節流函數封裝到一個通用工具庫中,例如:
export function debounce(fn, delay) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
export function throttle(fn, delay) {
let last = 0;
return function (...args) {
const now = +new Date();
if (now - last > delay) {
fn.apply(this, args);
last = now;
}
};
}
然后在組件中導入使用:
import { debounce } from './utils';
input.addEventListener('input', debounce(fetchSuggestions, 300));
?? 五、實際應用案例分析
1. 輸入框搜索建議(防抖)
<input type="text" id="searchInput">
<script>
const input = document.getElementById('searchInput');
function fetchSuggestions(query) {
console.log('發送請求:', query);
}
input.addEventListener('input', debounce((e) => {
fetchSuggestions(e.target.value);
}, 300));
</script>
2. 圖片懶加載 + 滾動監聽(節流)
<img data-src="image1.jpg" class="lazy-img">
<script>
function lazyLoadImages() {
const images = document.querySelectorAll('.lazy-img');
images.forEach(img => {
if (isInViewport(img)) {
img.src = img.dataset.src;
}
});
}
window.addEventListener('scroll', throttle(lazyLoadImages, 200));
</script>
關于圖片懶加載,想詳細了解可看前端性能優化實戰:一文搞懂圖片懶加載(Lazy Load),從原理到代碼全解析在圖片泛濫的網頁時代,加載速度決定用戶體驗 - 掘金
?? 六、拓展知識:高階函數 & 閉包的應用
1. 高階函數(Higher-order Function)
防抖和節流函數都是典型的高階函數,它們:
- 接收一個函數作為參數
- 返回一個新的函數(包裝后的函數)
這種設計模式廣泛應用于現代 JS 開發中,尤其在 React、Vue 等框架中。
2. 閉包(Closure)
在防抖和節流函數中,我們利用了閉包來保存狀態(如 timer
、last
、fn.id
等),這使得函數可以在多次調用之間共享狀態,而不污染全局作用域。
?? 七、結語:何時用防抖?何時用節流?
場景 | 推薦技術 |
---|
用戶輸入搜索建議 | ? 防抖 |
窗口調整大小 | ? 節流 |
滾動加載更多內容 | ? 節流 |
頻繁點擊按鈕 | ? 防抖 |
實時數據同步(如聊天輸入) | ? 防抖 |
動畫幀控制 | ? 節流 |
轉自https://juejin.cn/post/7525277602245574691
該文章在 2025/7/11 10:28:05 編輯過