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