本文共 3163 字,大约阅读时间需要 10 分钟。
要理解防抖與節流,我認為就要先去了解實際的業務場景有什麼問題,從而引出這樣的需求。我們從一個常見的場景來說起,大家一定都知道很多頁面都會有滾動條,其實所謂的拖動滾動條就是一個 scroll
事件。那麼我們現在來監聽一個指標,我們來監聽瀏覽器滾動事件,並且返回當前滾動條與頂部的距離。這個需求也不難,代碼如下:
function showTop() { let scrollTop = document.body.scrollTop || document.documentElement.scrollTop console.log("滚动条位置:" + scrollTop)}window.onscroll = showTop
把這段腳本貼到瀏覽器中,並且就以百度的網頁為例,開始拖動,效果如下:
scroll
事件,而且是非常高,即使只是按一下 [下方鍵] 都會觸發 8~9 次。 而在實際的業務場景下,這種情況是很不好的,大量的浪費了系統的性能,會造成性能上的缺陷,因此我們就引出本文的主角,防抖與節流,也就是為了解決這樣的問題,是一種前端優化的技術。
基於上述場景,我們提出第一種解決方案,防抖。思路是:我們在第一次觸發事件時不立即執行函數,而是給出一個計時器,或是說期限值,比如說 200ms,然後:
既然都提到了計時器,那麼我們借助 setTimeout()
來實現,而由於還需要一個變量來保存計時,所以我們使用閉包來實現。我們來看看代碼實現:
/** * fn [Function] : 需要防抖的函數 * delay [Number] : 防抖期限值 */function debounce(fn, delay) { let timer = null // 閉包 return function() { if(timer) { clearTimeout(timer) } timer = setTimeout(fn, delay) }}
具體使用起來如下:
function showTop() { let scrollTop = document.body.scrollTop || document.documentElement.scrollTop console.log("滚动条位置:" + scrollTop)}window.onscroll = debounce(showTop, 1000)
現在來看看效果:
我們會發現,此時如果你一直滑動滾動條,其實是不會觸發滾動事件的,只有當我們停止滾動 1 秒後,才會觸發滾動事件。
其實這樣就算是基本防抖的實現,最後來看看對於防抖的定義:
對於短時間內連續觸發的密集事件(例如
scroll
事件),防抖的目的就是限制在某個時間期限內(1000ms),事件處理函數只會執行 1 次。
接續上面的防抖,我們再想一下想一下用防抖處理的結果是:
但是其實這樣好像也不太對,正常情況下,可能要求的業務場景會是:
那麼這就是節流所要實現的。思路大致上是,我們設計一個類似閘門一樣定期開放的函數,也就是說讓函數在執行完一次之後,會失效一個時間段,過了這個時間段,又會再被重新激活。
這麼做效果就會是,如果短時間內大量觸發一個事件,那麼函數在執行一次之後,在指定期限內,即使用戶仍然繼續觸發事件,也不會響應,直到過了這段時限,用戶的觸發才會再被響應。
我們同樣借助 setTimeout()
來實現,並加上一個狀態 valid
來標示函數當前是否處於工作狀態。
/** * fn [Function] : 需要節流的函數 * delay [Number] : 節流期限值 */function throttle(fn, delay) { let valid = true return function() { if(!valid) { return } valid = false setTimeout(() => { fn() valid = true }, delay) }}
具體使用起來如下:
function showTop() { let scrollTop = document.body.scrollTop || document.documentElement.scrollTop console.log("滚动条位置:" + scrollTop)}window.onscroll = throttle(showTop, 1000)
現在來看看效果:
我們可以看到,現在如果用戶不斷地拖動滾動條,也會在 1 秒的間隔觸發滾動事件,而如果停止拖動,當然就不會觸發。
上面我們通過防抖跟節流核心的思想完成了優化,但是需要注意到的是,因為上面的 showTop
本身很簡單,所以我們都沒有考慮到別的問題,實際上,我們還要考慮到參數,以及 this 的問題(上下文環境)。下面我們給出一個通用的防抖以及節流的 API。下面用到了 apply
,如果不清楚歡迎參考 。
function debounce(fn, delay) { let timer = null return function() { // 將 this 指向實際返回的函數 let context = this if(timer) { clearTimeout(timer) timer = null } timer = setTimeout(() => { fn.apply(context, ...arguments) }, delay) }}
function throttle(fn, delay) { let valid = true return function() { // 將 this 指向實際返回的函數 let context = this if(!valid) { return } valid = false setTimeout(() => { valid = true fn.apply(context, ...arguments) }, delay) }}
防抖跟節流的技術非常重要,根據不同場景都可能會要應用到。
input
事件,需要支持說每隔多少秒就要搜索輸入,做到一個實時搜索的效果,這就可以使用節流。resize
事件,常見於需要做到頁面適配的話,需要根據頁面最終的狀況進行 DOM 的重新渲染,這種時候一般就是使用防抖,因為因為只需要判斷最後一次的變化情況。转载地址:http://iumo.baihongyu.com/