JS throttle與debounce的區別
一般在項目中我們會對input、scroll、resize等事件進行節流控制,防止事件過多觸發,減少資源消耗;在vue的官網的例子中就有關於lodash的debounce方法的使用,當時也提到了throttle,但一直沒搞明白節流 throttle 與 去抖 debounce具體區別在哪裏,所以花了點時間來搞清楚。
1. 區別
節流 throttle 與 去抖 debounce的區別主要在觸發時機上:
-
debounce(func, wait, options)
:創建並返回函數的防反跳版本,將延遲函數的執行(真正的執行)在函數最後一次調用時刻的wait毫秒之後,對於必須在一些輸入(多是一些用戶操作)停止之後再執行的行爲有幫助。將一個連續的調用歸爲一個,如果連續在wait毫秒內調用,最後只有最後一次會執行 -
throttle(func, wait, options)
:創建並返回一個像節流閥一樣的函數,當重複調用函數的時候,最多每隔指定的wait毫秒調用一次該函數;不允許方法在每wait毫秒間執行超過一次,如果連續在wait毫秒內調用,最後執行會均勻分佈在大約每wait一次
對於lodash來說,throttle是調用debounce來實現的,throttle 和 debounce 最終都會都會調用 debounce 方法。當調用 _.debounce
lodash會返回一個函數,這個函數在被調用時會生成一個 setTimeout(delayed, delay)。其中 delayed 又是一個內部方法,在 delayed 被調用時進行如下檢測:當前時間 - 上次func被調用事件 是否 小於 0 或 大於 delay ?如果是則執行一次 func,記錄並返回執行結果,同時更新上次被調用時間;如果不是則調用 setTimeout 進行下一次的判斷。_.throttle
方法只不過是多給 debounce 傳了一個 options = {maxWait: $maxWait, leading: true, trailing: true}
,這個選項的意思是至少保證在每 maxWait 時間讓 func 被調用一次。
可以看下面的栗子:
這個圖中圖中每個小格大約30ms,右邊有原生mouseover事件、lodash與jQuery節流去抖插件的debounce與throttle事件。 在圖左區域移動鼠標時:對於debounce,mouseover事件一直沒有被調用,直到停下來才被調用一次。而throttle是每wait毫秒就調用一次。
2. 使用場景
debounce:第一次觸發後,進行倒計wait毫秒,如果倒計時過程中有其他觸發,則重置倒計時;否則執行。用它來丟棄一些重複的密集操作,直到流量減慢。
throttle:第一次觸發後先執行fn(lodash可以通過{leading: false}
來取消),然後wait ms後再次執行,在單位wait毫秒內的所有重複觸發都被拋棄。即如果有連續不斷的觸發,每wait ms執行fn一次,用在每隔一定間隔執行回調的場景。
- mouse move 時減少計算次數:debounce
- input 中輸入文字自動發送 ajax 請求進行自動補全: debounce
- ajax 請求合併,不希望短時間內大量的請求被重複發送:debounce
- resize window 重新計算樣式或佈局:debounce 或 throttle
- scroll 時觸發操作,如隨動效果:throttle
- 對用戶輸入的驗證,不想停止輸入再進行驗證,而是每n秒進行驗證:throttle
3. 簡單實現
3.1 去抖 debounce
按照上面的說明,去抖就是連續多次delay內的操作取最後一次操作真正執行。
let reduceEvent function debounce(cb, delay) { if (!reduceEvent) { reduceEvent = setTimeout(() => { cb() console.log('執行啦!!') reduceEvent = null }, delay) } } setTimeout(() => debounce(() => console.log(1), 2000), 1000) // 打印: 1 執行啦!! setTimeout(() => debounce(() => console.log(2), 2000), 2000) setTimeout(() => debounce(() => console.log(3), 2000), 2000) setTimeout(() => debounce(() => console.log(4), 2000), 4000) // 打印: 4 執行啦!!
3.2 節流 throttle
按照上面的說明,節流就是連續多次delay內的操作按照指定的間隔來執行。
function throttle(func, wait = 200) { let last = 1 let timer return function(...rest) { const now = +new Date() if (last && now - last < wait) { clearTimeout(timer) timer = setTimeout(() => { last = now func.apply(this, rest) }, wait) } else { last = now func.apply(this, rest) clearTimeout(timer) } } } const task = throttle(() => console.log(1), 2000) setTimeout(task, 0) setTimeout(task, 500) setTimeout(task, 1000) setTimeout(task, 2000) // 打印: 1 1
網上的帖子大多深淺不一,甚至有些前後矛盾,在下的文章都是學習過程中的總結,如果發現錯誤,歡迎留言指出~
參考: