JavaScript防抖與節(jié)流詳解
防抖(Debounce)
一、防抖的定義
防抖是一種 優(yōu)化高頻觸發(fā)事件 的技術(shù),其核心思想是:在事件被頻繁觸發(fā)時,只有最后一次操作會被執(zhí)行,中間的觸發(fā)會被忽略。
- 典型場景:輸入框?qū)崟r搜索、窗口大小調(diào)整、滾動事件等需要限制執(zhí)行頻率的場景。
- 核心目標:減少不必要的計算或請求,提升性能和用戶體驗。
二、防抖的實現(xiàn)原理
防抖的底層實現(xiàn)依賴以下技術(shù)點:
- 定時器(
setTimeout和clearTimeout):用于控制事件觸發(fā)的延遲時間。 - 閉包(Closure):保存定時器狀態(tài),確保多次觸發(fā)時能共享同一個定時器。
- 函數(shù)包裝:將原始函數(shù)包裝成防抖函數(shù),返回一個新函數(shù)供事件調(diào)用。
三、防抖的代碼實現(xiàn)
以下是一個支持 立即執(zhí)行 和 延遲執(zhí)行 的通用防抖函數(shù):
function debounce(func, wait, immediate = false) {
let timeout = null;
// 返回包裝后的防抖函數(shù)
return function (...args) {
const context = this;
// 如果定時器存在,清除之前的定時器(取消未執(zhí)行的延遲操作)
if (timeout) clearTimeout(timeout);
if (immediate) {
// 立即執(zhí)行模式:首次觸發(fā)立即執(zhí)行,后續(xù)在 wait 時間內(nèi)觸發(fā)則重新計時
const callNow = !timeout;
timeout = setTimeout(() => {
timeout = null; // 恢復(fù)可執(zhí)行狀態(tài)
}, wait);
if (callNow) func.apply(context, args);
} else {
// 延遲執(zhí)行模式:最后一次觸發(fā)后等待 wait 時間再執(zhí)行
timeout = setTimeout(() => {
func.apply(context, args);
}, wait);
}
};
}四、代碼解析
- 參數(shù)說明:
func:需要防抖的原始函數(shù)。wait:防抖等待時間(單位:毫秒)。immediate:是否立即執(zhí)行(true表示首次觸發(fā)立即執(zhí)行,后續(xù)觸發(fā)需等待)。
- 閉包保存狀態(tài):
timeout變量通過閉包保存定時器 ID,確保多次觸發(fā)共享同一狀態(tài)。apply方法的作用:- 確保原始函數(shù)
func的this指向正確(指向觸發(fā)事件的元素)。 - 傳遞事件參數(shù)(如
event對象)。
- 兩種模式的區(qū)別:
- 立即執(zhí)行模式:首次觸發(fā)立即執(zhí)行函數(shù),之后在
wait時間內(nèi)再次觸發(fā)會重新計時,直到停止觸發(fā)超過wait時間后,才能再次立即執(zhí)行。 - 延遲執(zhí)行模式:每次觸發(fā)都會重置計時,只有最后一次觸發(fā)后等待
wait時間才會執(zhí)行。
- 立即執(zhí)行模式:首次觸發(fā)立即執(zhí)行函數(shù),之后在
五、使用示例
1. 輸入框?qū)崟r搜索(延遲執(zhí)行模式)
<input type="text" id="searchInput" />
<script>
const searchInput = document.getElementById('searchInput');
// 原始搜索函數(shù)
function search(query) {
console.log('搜索關(guān)鍵詞:', query);
}
// 防抖處理(延遲執(zhí)行)
const debouncedSearch = debounce(search, 500);
// 綁定輸入事件
searchInput.addEventListener('input', function (e) {
debouncedSearch(e.target.value);
});
</script>2. 按鈕防重復(fù)點擊(立即執(zhí)行模式)
<button id="submitBtn">提交</button>
<script>
const submitBtn = document.getElementById('submitBtn');
// 原始提交函數(shù)
function submitForm() {
console.log('表單已提交');
}
// 防抖處理(立即執(zhí)行)
const debouncedSubmit = debounce(submitForm, 1000, true);
// 綁定點擊事件
submitBtn.addEventListener('click', debouncedSubmit);
</script>六、總結(jié)
- 防抖的核心邏輯:通過定時器和閉包控制高頻事件的執(zhí)行時機。
- 實現(xiàn)要點:
- 使用
setTimeout和clearTimeout管理延遲。 - 通過閉包保存定時器狀態(tài)。
- 處理
this指向和參數(shù)傳遞。
- 使用
- 應(yīng)用場景:
- 輸入框?qū)崟r搜索建議。
- 窗口大小調(diào)整后的布局計算。
- 防止按鈕重復(fù)提交。
節(jié)流(Throttle)
一、節(jié)流的定義
節(jié)流是一種 限制高頻觸發(fā)事件執(zhí)行頻率 的技術(shù),其核心思想是:在事件被頻繁觸發(fā)時,固定時間間隔內(nèi)只執(zhí)行一次操作,忽略中間的觸發(fā)。
- 典型場景:滾動事件、鼠標移動事件(如拖拽)、窗口大小調(diào)整、按鈕頻繁點擊等。
- 核心目標:在保證功能正常的前提下,降低事件處理頻率,優(yōu)化性能。
二、節(jié)流的實現(xiàn)原理
節(jié)流的底層實現(xiàn)依賴以下技術(shù)點:
- 定時器(
setTimeout和clearTimeout)或時間戳:用于控制事件觸發(fā)的間隔時間。 - 閉包(Closure):保存計時器狀態(tài),確保多次觸發(fā)時能共享同一狀態(tài)。
- 函數(shù)包裝:將原始函數(shù)包裝成節(jié)流函數(shù),返回一個新函數(shù)供事件調(diào)用。
三、節(jié)流的代碼實現(xiàn)
以下是兩種常見的節(jié)流實現(xiàn)方式:
1. 時間戳方式(立即執(zhí)行)
首次觸發(fā)立即執(zhí)行,之后在固定間隔內(nèi)忽略后續(xù)觸發(fā)。
function throttle(func, wait) {
let previous = 0; // 上次執(zhí)行時間戳
return function (...args) {
const now = Date.now();
const context = this;
if (now - previous > wait) {
func.apply(context, args);
previous = now; // 更新執(zhí)行時間戳
}
};
}2. 定時器方式(延遲執(zhí)行)
首次觸發(fā)后等待固定時間執(zhí)行,之后在固定間隔內(nèi)忽略后續(xù)觸發(fā)。
function throttle(func, wait) {
let timeout = null;
return function (...args) {
const context = this;
if (!timeout) {
timeout = setTimeout(() => {
timeout = null; // 重置定時器
func.apply(context, args);
}, wait);
}
};
}3. 結(jié)合時間戳和定時器(首尾均執(zhí)行)
首次觸發(fā)立即執(zhí)行,最后一次觸發(fā)在間隔結(jié)束后再執(zhí)行一次。
function throttle(func, wait) {
let previous = 0;
let timeout = null;
return function (...args) {
const context = this;
const now = Date.now();
const remaining = wait - (now - previous);
if (remaining <= 0) {
// 立即執(zhí)行
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
func.apply(context, args);
previous = now;
} else if (!timeout) {
// 設(shè)置最后一次執(zhí)行的定時器
timeout = setTimeout(() => {
func.apply(context, args);
timeout = null;
previous = Date.now();
}, remaining);
}
};
}四、代碼解析
- 參數(shù)說明:
func:需要節(jié)流的原始函數(shù)。wait:節(jié)流間隔時間(單位:毫秒)。
- 閉包保存狀態(tài):
previous(時間戳方式)或timeout(定時器方式)通過閉包保存狀態(tài),確保多次觸發(fā)共享同一計時器。
apply方法的作用:- 確保原始函數(shù)
func的this指向正確(如事件觸發(fā)的元素)。 - 傳遞事件參數(shù)(如
event對象)。
- 確保原始函數(shù)
- 不同實現(xiàn)方式的區(qū)別:
- 時間戳方式:立即響應(yīng)首次觸發(fā),適合需要即時反饋的場景(如按鈕點擊)。
- 定時器方式:延遲響應(yīng)首次觸發(fā),適合連續(xù)觸發(fā)但不需要立即執(zhí)行的場景(如滾動事件)。
- 結(jié)合方式:兼顧首尾執(zhí)行,適用于需要更平滑響應(yīng)的場景(如動畫)。
五、使用示例
1. 滾動事件節(jié)流(時間戳方式)
<div id="scrollArea" style="height: 2000px;"></div>
<script>
const scrollArea = document.getElementById('scrollArea');
// 原始滾動處理函數(shù)
function handleScroll() {
console.log('滾動位置:', window.scrollY);
}
// 節(jié)流處理(時間戳方式)
const throttledScroll = throttle(handleScroll, 200);
// 綁定滾動事件
window.addEventListener('scroll', throttledScroll);
</script>2. 按鈕防重復(fù)點擊(定時器方式)
<button id="clickBtn">點擊</button>
<script>
const clickBtn = document.getElementById('clickBtn');
// 原始點擊處理函數(shù)
function handleClick() {
console.log('按鈕點擊');
}
// 節(jié)流處理(定時器方式)
const throttledClick = throttle(handleClick, 1000);
// 綁定點擊事件
clickBtn.addEventListener('click', throttledClick);
</script>六、總結(jié)
- 節(jié)流的核心邏輯:通過時間戳或定時器控制高頻事件的執(zhí)行頻率。
- 實現(xiàn)要點:
- 使用
Date.now()或setTimeout管理間隔時間。 - 通過閉包保存計時器或時間戳狀態(tài)。
- 處理
this指向和參數(shù)傳遞。
- 使用
- 應(yīng)用場景:
- 滾動事件觸發(fā)加載更多內(nèi)容。
- 鼠標移動時更新元素位置(如拖拽)。
- 防止按鈕頻繁點擊導(dǎo)致的重復(fù)提交。
對比:防抖 vs 節(jié)流
1. 防抖(debounce)
所謂防抖,就是指觸發(fā)事件后在 n 秒內(nèi)函數(shù)只能執(zhí)行一次,如果在 n 秒內(nèi)又觸發(fā)了事件,則會重新計算函數(shù)執(zhí)行時間
//防抖簡單寫法
function debounce(func, t) {
let timer = null
return function () {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => func(), t)
}
}2. 節(jié)流(throttle)
所謂節(jié)流,就是指連續(xù)觸發(fā)事件但是在 n 秒中只執(zhí)行一次函數(shù)
//節(jié)流簡單寫法
function throttle(func, t) {
let timer = null
return function () {
if (!timer) {
timer = setTimeout(() => {
func()
timer = null
}, t)
}
}
}3. 對比:
| 特性 | 防抖(Debounce) | 節(jié)流(Throttle) |
|---|---|---|
| 觸發(fā)頻率 | 最后一次觸發(fā)后等待 wait 時間執(zhí)行 | 固定時間間隔內(nèi)最多執(zhí)行一次 |
| 適用場景 | 輸入框搜索、窗口大小調(diào)整 | 滾動事件、鼠標移動事件、頻繁點擊按鈕 |
| 核心目標 | 確保高頻觸發(fā)時只執(zhí)行一次 | 確保高頻觸發(fā)時按固定頻率執(zhí)行 |
源碼解析
一、_.throttle()源碼解析
1. 核心邏輯
節(jié)流函數(shù)確保在 wait 時間間隔內(nèi)最多執(zhí)行一次 func,支持首次(leading)和末次(trailing)執(zhí)行控制。
2. 關(guān)鍵源碼步驟
_.throttle(func, [wait=0], [options={}])function throttle(func, wait, options) {
let leading = true;
let trailing = true;
// 參數(shù)處理
if (typeof options === 'object') {
leading = 'leading' in options ? !!options.leading : leading;
trailing = 'trailing' in options ? !!options.trailing : trailing;
}
let lastArgs, lastThis, result;
let timeout = null;
let previous = 0;
const throttled = function(...args) {
const now = Date.now();
// 首次調(diào)用且不執(zhí)行 leading 時,設(shè)置 previous 為 now
if (!previous && leading === false) previous = now;
// 計算剩余時間
const remaining = wait - (now - previous);
// 需要執(zhí)行(剩余時間 <=0 或系統(tǒng)時間被修改)
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(this, args);
} else if (!timeout && trailing !== false) {
// 設(shè)置尾調(diào)用的定時器
timeout = setTimeout(() => {
previous = leading === false ? 0 : Date.now();
timeout = null;
result = func.apply(lastThis, lastArgs);
}, remaining);
}
return result;
};
// 提供取消方法
throttled.cancel = function() {
clearTimeout(timeout);
previous = 0;
timeout = lastArgs = lastThis = null;
};
return throttled;
}3. 核心特性
- 時間戳與定時器結(jié)合:既保證首次觸發(fā)立即響應(yīng)(
leading),又在停止觸發(fā)后執(zhí)行最后一次(trailing)。 - 系統(tǒng)時間篡改兼容:檢測到
remaining > wait時強制觸發(fā)。 - 取消機制:允許手動取消未執(zhí)行的尾調(diào)用。
二、_.debounce()源碼解析
1. 核心邏輯
防抖函數(shù)在連續(xù)觸發(fā)時,僅在最后一次觸發(fā)后等待 wait 時間執(zhí)行一次 func,支持立即執(zhí)行模式(leading)。
2. 關(guān)鍵源碼步驟
_.debounce(func, [wait=0], [options={}])function debounce(func, wait, options) {
let lastArgs, lastThis, result;
let timerId = null;
let lastCallTime = 0;
let leading = false;
let maxing = false;
let maxWait;
// 參數(shù)處理
if (typeof options === 'object') {
leading = !!options.leading;
maxing = 'maxWait' in options;
maxWait = maxing ? Math.max(options.maxWait || 0, wait) : maxWait;
}
const invokeFunc = (time) => {
const args = lastArgs;
const thisArg = lastThis;
lastArgs = lastThis = undefined;
result = func.apply(thisArg, args);
return result;
};
const leadingEdge = (time) => {
// 記錄最后一次調(diào)用時間
lastCallTime = time;
// 設(shè)置定時器
timerId = setTimeout(timerExpired, wait);
// 立即執(zhí)行模式
return leading ? invokeFunc(time) : result;
};
const shouldInvoke = (time) => {
// 判斷是否需要執(zhí)行(超過 wait 或 maxWait)
const timeSinceLastCall = time - lastCallTime;
return (lastCallTime === 0) || (timeSinceLastCall >= wait) ||
(maxing && timeSinceLastCall >= maxWait);
};
const debounced = function(...args) {
const time = Date.now();
lastArgs = args;
lastThis = this;
// 判斷是否應(yīng)該執(zhí)行
const isInvoking = shouldInvoke(time);
if (isInvoking) {
// 清除已有定時器
if (timerId === null) {
return leadingEdge(time);
}
// 處理 maxWait 場景
if (maxing) {
timerId = setTimeout(timerExpired, wait);
return invokeFunc(time);
}
}
// 設(shè)置/重置定時器
if (timerId === null) {
timerId = setTimeout(timerExpired, wait);
}
return result;
};
// 提供取消和立即執(zhí)行方法
debounced.cancel = function() { /* ... */ };
debounced.flush = function() { /* ... */ };
return debounced;
}3. 核心特性
maxWait支持:確保在超時后強制執(zhí)行,避免長期不觸發(fā)導(dǎo)致的延遲。- 立即執(zhí)行模式:
leading選項允許首次觸發(fā)立即執(zhí)行。 - 靈活控制:
cancel和flush方法提供外部控制能力。
到此這篇關(guān)于JavaScript防抖與節(jié)流的文章就介紹到這了,更多相關(guān)js 防抖與節(jié)流內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- JavaScript函數(shù)中的防抖與節(jié)流原生實現(xiàn)及第三方庫的使用
- JavaScript中防抖和節(jié)流的原理和區(qū)別詳解
- 淺談JavaScript中的防抖和節(jié)流
- JS面試之手寫節(jié)流防抖詳解
- JavaScript防抖與節(jié)流超詳細全面講解
- 利用JavaScript實現(xiàn)防抖節(jié)流函數(shù)的示例代碼
- JavaScript函數(shù)防抖與函數(shù)節(jié)流的定義及使用詳解
- JS中節(jié)流和防抖函數(shù)的實現(xiàn)及區(qū)別示例
- JavaScript中防抖和節(jié)流的區(qū)別及適用場景
相關(guān)文章
JavaScript 處理Iframe自適應(yīng)高度(同或不同域名下)
Iframe自適應(yīng)高度一直都備受關(guān)注,接下來為大家介紹下同域名下Iframe自適應(yīng)高度的處理以及跨域時Iframe高度自適應(yīng),感興趣的朋友可以參考下哈2013-03-03
JavaScript中將一個值轉(zhuǎn)換為字符串的方法分析[譯]
在JavaScript中,主要有三種方法能讓任意值轉(zhuǎn)換為字符串.本文講解了每種方法以及各自的優(yōu)缺點2012-09-09

