JavaScript防抖與節(jié)流詳解
防抖(Debounce)
一、防抖的定義
防抖是一種 優(yōu)化高頻觸發(fā)事件 的技術(shù),其核心思想是:在事件被頻繁觸發(fā)時(shí),只有最后一次操作會(huì)被執(zhí)行,中間的觸發(fā)會(huì)被忽略。
- 典型場(chǎng)景:輸入框?qū)崟r(shí)搜索、窗口大小調(diào)整、滾動(dòng)事件等需要限制執(zhí)行頻率的場(chǎng)景。
- 核心目標(biāo):減少不必要的計(jì)算或請(qǐng)求,提升性能和用戶體驗(yàn)。
二、防抖的實(shí)現(xiàn)原理
防抖的底層實(shí)現(xiàn)依賴以下技術(shù)點(diǎn):
- 定時(shí)器(
setTimeout和clearTimeout):用于控制事件觸發(fā)的延遲時(shí)間。 - 閉包(Closure):保存定時(shí)器狀態(tài),確保多次觸發(fā)時(shí)能共享同一個(gè)定時(shí)器。
- 函數(shù)包裝:將原始函數(shù)包裝成防抖函數(shù),返回一個(gè)新函數(shù)供事件調(diào)用。
三、防抖的代碼實(shí)現(xiàn)
以下是一個(gè)支持 立即執(zhí)行 和 延遲執(zhí)行 的通用防抖函數(shù):
function debounce(func, wait, immediate = false) {
let timeout = null;
// 返回包裝后的防抖函數(shù)
return function (...args) {
const context = this;
// 如果定時(shí)器存在,清除之前的定時(shí)器(取消未執(zhí)行的延遲操作)
if (timeout) clearTimeout(timeout);
if (immediate) {
// 立即執(zhí)行模式:首次觸發(fā)立即執(zhí)行,后續(xù)在 wait 時(shí)間內(nèi)觸發(fā)則重新計(jì)時(shí)
const callNow = !timeout;
timeout = setTimeout(() => {
timeout = null; // 恢復(fù)可執(zhí)行狀態(tài)
}, wait);
if (callNow) func.apply(context, args);
} else {
// 延遲執(zhí)行模式:最后一次觸發(fā)后等待 wait 時(shí)間再執(zhí)行
timeout = setTimeout(() => {
func.apply(context, args);
}, wait);
}
};
}四、代碼解析
- 參數(shù)說(shuō)明:
func:需要防抖的原始函數(shù)。wait:防抖等待時(shí)間(單位:毫秒)。immediate:是否立即執(zhí)行(true表示首次觸發(fā)立即執(zhí)行,后續(xù)觸發(fā)需等待)。
- 閉包保存狀態(tài):
timeout變量通過(guò)閉包保存定時(shí)器 ID,確保多次觸發(fā)共享同一狀態(tài)。apply方法的作用:- 確保原始函數(shù)
func的this指向正確(指向觸發(fā)事件的元素)。 - 傳遞事件參數(shù)(如
event對(duì)象)。
- 兩種模式的區(qū)別:
- 立即執(zhí)行模式:首次觸發(fā)立即執(zhí)行函數(shù),之后在
wait時(shí)間內(nèi)再次觸發(fā)會(huì)重新計(jì)時(shí),直到停止觸發(fā)超過(guò)wait時(shí)間后,才能再次立即執(zhí)行。 - 延遲執(zhí)行模式:每次觸發(fā)都會(huì)重置計(jì)時(shí),只有最后一次觸發(fā)后等待
wait時(shí)間才會(huì)執(zhí)行。
- 立即執(zhí)行模式:首次觸發(fā)立即執(zhí)行函數(shù),之后在
五、使用示例
1. 輸入框?qū)崟r(shí)搜索(延遲執(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ù)點(diǎn)擊(立即執(zhí)行模式)
<button id="submitBtn">提交</button>
<script>
const submitBtn = document.getElementById('submitBtn');
// 原始提交函數(shù)
function submitForm() {
console.log('表單已提交');
}
// 防抖處理(立即執(zhí)行)
const debouncedSubmit = debounce(submitForm, 1000, true);
// 綁定點(diǎn)擊事件
submitBtn.addEventListener('click', debouncedSubmit);
</script>六、總結(jié)
- 防抖的核心邏輯:通過(guò)定時(shí)器和閉包控制高頻事件的執(zhí)行時(shí)機(jī)。
- 實(shí)現(xiàn)要點(diǎn):
- 使用
setTimeout和clearTimeout管理延遲。 - 通過(guò)閉包保存定時(shí)器狀態(tài)。
- 處理
this指向和參數(shù)傳遞。
- 使用
- 應(yīng)用場(chǎng)景:
- 輸入框?qū)崟r(shí)搜索建議。
- 窗口大小調(diào)整后的布局計(jì)算。
- 防止按鈕重復(fù)提交。
節(jié)流(Throttle)
一、節(jié)流的定義
節(jié)流是一種 限制高頻觸發(fā)事件執(zhí)行頻率 的技術(shù),其核心思想是:在事件被頻繁觸發(fā)時(shí),固定時(shí)間間隔內(nèi)只執(zhí)行一次操作,忽略中間的觸發(fā)。
- 典型場(chǎng)景:滾動(dòng)事件、鼠標(biāo)移動(dòng)事件(如拖拽)、窗口大小調(diào)整、按鈕頻繁點(diǎn)擊等。
- 核心目標(biāo):在保證功能正常的前提下,降低事件處理頻率,優(yōu)化性能。
二、節(jié)流的實(shí)現(xiàn)原理
節(jié)流的底層實(shí)現(xiàn)依賴以下技術(shù)點(diǎn):
- 定時(shí)器(
setTimeout和clearTimeout)或時(shí)間戳:用于控制事件觸發(fā)的間隔時(shí)間。 - 閉包(Closure):保存計(jì)時(shí)器狀態(tài),確保多次觸發(fā)時(shí)能共享同一狀態(tài)。
- 函數(shù)包裝:將原始函數(shù)包裝成節(jié)流函數(shù),返回一個(gè)新函數(shù)供事件調(diào)用。
三、節(jié)流的代碼實(shí)現(xiàn)
以下是兩種常見(jiàn)的節(jié)流實(shí)現(xiàn)方式:
1. 時(shí)間戳方式(立即執(zhí)行)
首次觸發(fā)立即執(zhí)行,之后在固定間隔內(nèi)忽略后續(xù)觸發(fā)。
function throttle(func, wait) {
let previous = 0; // 上次執(zhí)行時(shí)間戳
return function (...args) {
const now = Date.now();
const context = this;
if (now - previous > wait) {
func.apply(context, args);
previous = now; // 更新執(zhí)行時(shí)間戳
}
};
}2. 定時(shí)器方式(延遲執(zhí)行)
首次觸發(fā)后等待固定時(shí)間執(zhí)行,之后在固定間隔內(nèi)忽略后續(xù)觸發(fā)。
function throttle(func, wait) {
let timeout = null;
return function (...args) {
const context = this;
if (!timeout) {
timeout = setTimeout(() => {
timeout = null; // 重置定時(shí)器
func.apply(context, args);
}, wait);
}
};
}3. 結(jié)合時(shí)間戳和定時(shí)器(首尾均執(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í)行的定時(shí)器
timeout = setTimeout(() => {
func.apply(context, args);
timeout = null;
previous = Date.now();
}, remaining);
}
};
}四、代碼解析
- 參數(shù)說(shuō)明:
func:需要節(jié)流的原始函數(shù)。wait:節(jié)流間隔時(shí)間(單位:毫秒)。
- 閉包保存狀態(tài):
previous(時(shí)間戳方式)或timeout(定時(shí)器方式)通過(guò)閉包保存狀態(tài),確保多次觸發(fā)共享同一計(jì)時(shí)器。
apply方法的作用:- 確保原始函數(shù)
func的this指向正確(如事件觸發(fā)的元素)。 - 傳遞事件參數(shù)(如
event對(duì)象)。
- 確保原始函數(shù)
- 不同實(shí)現(xiàn)方式的區(qū)別:
- 時(shí)間戳方式:立即響應(yīng)首次觸發(fā),適合需要即時(shí)反饋的場(chǎng)景(如按鈕點(diǎn)擊)。
- 定時(shí)器方式:延遲響應(yīng)首次觸發(fā),適合連續(xù)觸發(fā)但不需要立即執(zhí)行的場(chǎng)景(如滾動(dòng)事件)。
- 結(jié)合方式:兼顧首尾執(zhí)行,適用于需要更平滑響應(yīng)的場(chǎng)景(如動(dòng)畫)。
五、使用示例
1. 滾動(dòng)事件節(jié)流(時(shí)間戳方式)
<div id="scrollArea" style="height: 2000px;"></div>
<script>
const scrollArea = document.getElementById('scrollArea');
// 原始滾動(dòng)處理函數(shù)
function handleScroll() {
console.log('滾動(dòng)位置:', window.scrollY);
}
// 節(jié)流處理(時(shí)間戳方式)
const throttledScroll = throttle(handleScroll, 200);
// 綁定滾動(dòng)事件
window.addEventListener('scroll', throttledScroll);
</script>2. 按鈕防重復(fù)點(diǎn)擊(定時(shí)器方式)
<button id="clickBtn">點(diǎn)擊</button>
<script>
const clickBtn = document.getElementById('clickBtn');
// 原始點(diǎn)擊處理函數(shù)
function handleClick() {
console.log('按鈕點(diǎn)擊');
}
// 節(jié)流處理(定時(shí)器方式)
const throttledClick = throttle(handleClick, 1000);
// 綁定點(diǎn)擊事件
clickBtn.addEventListener('click', throttledClick);
</script>六、總結(jié)
- 節(jié)流的核心邏輯:通過(guò)時(shí)間戳或定時(shí)器控制高頻事件的執(zhí)行頻率。
- 實(shí)現(xiàn)要點(diǎn):
- 使用
Date.now()或setTimeout管理間隔時(shí)間。 - 通過(guò)閉包保存計(jì)時(shí)器或時(shí)間戳狀態(tài)。
- 處理
this指向和參數(shù)傳遞。
- 使用
- 應(yīng)用場(chǎng)景:
- 滾動(dòng)事件觸發(fā)加載更多內(nèi)容。
- 鼠標(biāo)移動(dòng)時(shí)更新元素位置(如拖拽)。
- 防止按鈕頻繁點(diǎn)擊導(dǎo)致的重復(fù)提交。
對(duì)比:防抖 vs 節(jié)流
1. 防抖(debounce)
所謂防抖,就是指觸發(fā)事件后在 n 秒內(nèi)函數(shù)只能執(zhí)行一次,如果在 n 秒內(nèi)又觸發(fā)了事件,則會(huì)重新計(jì)算函數(shù)執(zhí)行時(shí)間
//防抖簡(jiǎn)單寫法
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é)流簡(jiǎn)單寫法
function throttle(func, t) {
let timer = null
return function () {
if (!timer) {
timer = setTimeout(() => {
func()
timer = null
}, t)
}
}
}3. 對(duì)比:
| 特性 | 防抖(Debounce) | 節(jié)流(Throttle) |
|---|---|---|
| 觸發(fā)頻率 | 最后一次觸發(fā)后等待 wait 時(shí)間執(zhí)行 | 固定時(shí)間間隔內(nèi)最多執(zhí)行一次 |
| 適用場(chǎng)景 | 輸入框搜索、窗口大小調(diào)整 | 滾動(dòng)事件、鼠標(biāo)移動(dòng)事件、頻繁點(diǎn)擊按鈕 |
| 核心目標(biāo) | 確保高頻觸發(fā)時(shí)只執(zhí)行一次 | 確保高頻觸發(fā)時(shí)按固定頻率執(zhí)行 |
源碼解析
一、_.throttle()源碼解析
1. 核心邏輯
節(jié)流函數(shù)確保在 wait 時(shí)間間隔內(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í),設(shè)置 previous 為 now
if (!previous && leading === false) previous = now;
// 計(jì)算剩余時(shí)間
const remaining = wait - (now - previous);
// 需要執(zhí)行(剩余時(shí)間 <=0 或系統(tǒng)時(shí)間被修改)
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)用的定時(shí)器
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. 核心特性
- 時(shí)間戳與定時(shí)器結(jié)合:既保證首次觸發(fā)立即響應(yīng)(
leading),又在停止觸發(fā)后執(zhí)行最后一次(trailing)。 - 系統(tǒng)時(shí)間篡改兼容:檢測(cè)到
remaining > wait時(shí)強(qiáng)制觸發(fā)。 - 取消機(jī)制:允許手動(dòng)取消未執(zhí)行的尾調(diào)用。
二、_.debounce()源碼解析
1. 核心邏輯
防抖函數(shù)在連續(xù)觸發(fā)時(shí),僅在最后一次觸發(fā)后等待 wait 時(shí)間執(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)用時(shí)間
lastCallTime = time;
// 設(shè)置定時(shí)器
timerId = setTimeout(timerExpired, wait);
// 立即執(zhí)行模式
return leading ? invokeFunc(time) : result;
};
const shouldInvoke = (time) => {
// 判斷是否需要執(zhí)行(超過(guò) 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) {
// 清除已有定時(shí)器
if (timerId === null) {
return leadingEdge(time);
}
// 處理 maxWait 場(chǎng)景
if (maxing) {
timerId = setTimeout(timerExpired, wait);
return invokeFunc(time);
}
}
// 設(shè)置/重置定時(shí)器
if (timerId === null) {
timerId = setTimeout(timerExpired, wait);
}
return result;
};
// 提供取消和立即執(zhí)行方法
debounced.cancel = function() { /* ... */ };
debounced.flush = function() { /* ... */ };
return debounced;
}3. 核心特性
maxWait支持:確保在超時(shí)后強(qiáng)制執(zhí)行,避免長(zhǎng)期不觸發(fā)導(dǎo)致的延遲。- 立即執(zhí)行模式:
leading選項(xiàng)允許首次觸發(fā)立即執(zhí)行。 - 靈活控制:
cancel和flush方法提供外部控制能力。
到此這篇關(guān)于JavaScript防抖與節(jié)流的文章就介紹到這了,更多相關(guān)js 防抖與節(jié)流內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- JavaScript函數(shù)中的防抖與節(jié)流原生實(shí)現(xiàn)及第三方庫(kù)的使用
- JavaScript中防抖和節(jié)流的原理和區(qū)別詳解
- 淺談JavaScript中的防抖和節(jié)流
- JS面試之手寫節(jié)流防抖詳解
- JavaScript防抖與節(jié)流超詳細(xì)全面講解
- 利用JavaScript實(shí)現(xiàn)防抖節(jié)流函數(shù)的示例代碼
- JavaScript函數(shù)防抖與函數(shù)節(jié)流的定義及使用詳解
- JS中節(jié)流和防抖函數(shù)的實(shí)現(xiàn)及區(qū)別示例
- JavaScript中防抖和節(jié)流的區(qū)別及適用場(chǎng)景
相關(guān)文章
js實(shí)現(xiàn)簡(jiǎn)單抽獎(jiǎng)功能
這篇文章主要為大家詳細(xì)介紹了js實(shí)現(xiàn)簡(jiǎn)單抽獎(jiǎng)功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-09-09
Bootstrap基本樣式學(xué)習(xí)筆記之表單(3)
這篇文章主要介紹了Bootstrap學(xué)習(xí)筆記之表單基本樣式的相關(guān)資料,為大家分享了三種表單樣式,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12
CSS和Javascript簡(jiǎn)單復(fù)習(xí)資料
CSS和Javascript簡(jiǎn)單復(fù)習(xí)資料,學(xué)習(xí)css與js的朋友可以參考下。2010-06-06
JavaScript中函數(shù)聲明與函數(shù)表達(dá)式的區(qū)別詳解
可能很多朋友只知道兩種聲明方式一個(gè)是函數(shù)聲明一個(gè)是函數(shù)表達(dá)式,具體有什么不同沒(méi)能說(shuō)得很好。事實(shí)上,JavaScript的解析器對(duì)函數(shù)聲明與函數(shù)表達(dá)式并不是一視同仁地對(duì)待的。下面看看這兩者到底有什么不同。2016-08-08
elementui-樹形控件實(shí)現(xiàn)子節(jié)點(diǎn)右側(cè)添加圖標(biāo)和數(shù)據(jù)鼠標(biāo)放上去顯示文字效果
這篇文章主要介紹了elementui-樹形控件實(shí)現(xiàn)子節(jié)點(diǎn)右側(cè)添加圖標(biāo)和數(shù)據(jù)鼠標(biāo)放上去顯示文字效果,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧2024-01-01
JavaScript實(shí)現(xiàn)點(diǎn)擊按鈕后變灰避免多次重復(fù)提交
注冊(cè)的時(shí)候需要發(fā)送驗(yàn)證激活帳號(hào)的郵件,為了避免郵件的多次重復(fù)發(fā)送,所以可以在點(diǎn)擊了發(fā)送后,設(shè)置按鈕變灰,倒計(jì)時(shí)一段時(shí)間后又可重復(fù)點(diǎn)擊,具體實(shí)現(xiàn)如下,感興趣的朋友可以參考下哈2013-07-07
基于JavaScript實(shí)現(xiàn)網(wǎng)頁(yè)版羊了個(gè)羊游戲
最近羊了個(gè)羊火的不得了,這篇文章主要為大家介紹了如何利用JS實(shí)現(xiàn)個(gè)網(wǎng)頁(yè)版羊了個(gè)羊游戲,有需要的朋友可以借鑒參考下,希望能夠有所幫助2022-09-09
自己寫的Javascript計(jì)算時(shí)間差函數(shù)
Javascript計(jì)算時(shí)間差函數(shù),獲得時(shí)間差,時(shí)間格式為 年-月-日 小時(shí):分鐘:秒 或者 年/月/日 小時(shí):分鐘:秒。2013-10-10

