爲什麼需要函數防抖和函數節流?
在瀏覽器中某些計算和處理要比其他的昂貴很多。例如DOM操作比起非DOM交互需要更多的內存和CPU佔用時間。連續進行過多的DOM操作可能會導致瀏覽器掛起,甚至崩潰;例如當調整瀏覽器大小的時候,resize事件會連續觸發;如果在resize事件內部進行DOM操作,高頻率的更改可能會導致瀏覽器崩潰;爲了解決上面的問題,就有了函數節流和防抖。
什麼是函數防抖和函數節流?
防抖(debounce)和節流(throttle)都是用來控制某個函數在一定時間內執行多少次的技巧,兩者相似卻又不同。 背後的基本思想是某些代碼不可以在沒有間斷的情況下連續重複執行。
應用場景
類型 | 場景 |
---|---|
函數節流 | 1. DOM元素拖拽(mousemove) 2. Canvas 模擬畫板功能(mousemove) 3.鼠標不斷點擊觸發,mousedown(單位時間內只觸發一次) 4.監聽滾動事件,比如是否滑到底部自動加載更多,用throttle來判斷 |
函數防抖 | 1. 給按鈕加函數防抖防止表單多次提交 2. 對於輸入框連續輸入進行AJAX驗證時,用函數防抖能有效減少請求次數 3. 判斷scroll是否滑到底部,滾動事件+函數防抖 4.window觸發resize的時候,不斷的調整瀏覽器窗口大小會不斷的觸發這個事件,用防抖來讓其只觸發一次 |
防抖
如果一個事件被頻繁觸發多次,並且觸發的時間間隔過短,則防抖函數可以使得對應的事件處理函數只執行最後觸發的一次。 函數防抖可以把多個順序的調用合併成一次。即一個需要頻繁觸發的函數,在規定時間內,只讓最後一次生效,前面的不生效。我們可以聯想到帕金森綜合徵。
即觸發事件後在n秒內函數只能執行一次,如果在n秒內又觸發了事件,則會重新計算函數執行時間。
防抖函數分爲非立即執行版和立即執行版。
-
非立即執行版:即觸發事件後函數不會立即執行,而是在n秒後執行,如果在n秒內又觸發了事件,則會重新計算函數執行時間。
-
立即執行版:觸發事件後函數會立即執行,然後n秒內不觸發事件才能繼續執行函數的效果。
非立即執行版代碼實現
function debounce(fn, delay, scope) {
// 記錄上一次的延時器
let timer = null;
return function () {
// setTimeout()中函數環境總是window,故需要當前環境的副本;
let context = scope || this, args = arguments; // 保證最終返回的函數this指向不變以及依舊能接受到e參數
// 清除上一次延時器
if (timeout) clearTimeout(timeout); // 如果事件被觸發,清除timer並重新開始計時
timer = setTimeout(function () {
fn.apply(context, args);
}, delay);
}
}
// 代碼解讀
1. 第一次調用函數,創建一個定時器,在指定的時間間隔之後運行代碼;
2. 當第二次調用該函數時,它會清除前一次的定時器並設置另一個;
3. 如果前一個定時器已經執行過了,這個操作就沒有任何意義;
4. 如果前一個定時器尚未執行,其實就是將其替換爲一個新的定時器;
5. 只有在執行函數的請求停止了delay時間之後才執行。
立即執行版代碼實現
function debounce(func,wait,scope) {
let timer = null;
return function () {
let context = scope || this, args = arguments;
if (timeout) clearTimeout(timeout);
let callNow = !timeout; // 臨時變量
timeout = setTimeout(() => {
timeout = null;
}, wait)
if (callNow) func.apply(context, args)
}
}
節流
如果一個事件被頻繁觸發多次,節流函數可以按照固定頻率去執行對應的事件處理方法。 函數節流保證一個事件n秒內只執行一次。我們可以聯想到水龍頭
指連續觸發事件但是在n秒中只執行一次函數。 節流會稀釋函數的執行頻率。節流一般有兩種方式實現,分別是時間戳版和定時器版。
時間戳實現
// 即在持續觸發事件的過程中,函數會立即執行,並且每n秒執行一次
let prev = 0; // 記錄上一次函數觸發的時間
function throttle(fn, threshold, scope) {
let timer;
return function () {
let context = scope || this, args = arguments;
let now = Date.now(); // // 記錄當前函數觸發的時間
if (now - prev > threshold) {
prev = now; // 同步時間
fn.apply(context, args); // // 修正this指向
}
}
}
定時器實現
// 在持續觸發事件的過程中,函數不會立即執行,並且每n秒執行一次,在停止觸發事件後,函數還會再執行一次
function throttle2(fn, threshold, scope) {
let timer;
return function () {
let context = scope || this, args = arguments;
if (!timer) {
timer = setTimeout(function () {
fn.apply(context, args);
timer = null;
}, threshold)
}
}
}
時間戳和定時器的區別:
時間戳版的函數觸發是在時間段內開始的時候,而定時器版的函數觸發是在時間段內結束的時候。
總結
debounce和throttle均是通過減少高頻觸發事件的實際事件處理程序的執行來提高事件處理函數運行性能的手段,並沒有實質上減少事件的觸發次數。
debounce可以把多個順序的調用合併成一次。
throttle保證一個事件一定時間內只執行一次。
兩者的區別在於函數節流是固定時間做某一件事,比如每隔1秒發一次請求。而函數防抖是在頻繁觸發後,只執行一次(前提都是頻繁觸發)
文章有什麼不對的地方,歡迎指正!