函數節流與函數防抖
- 2019 年 12 月 5 日
- 筆記
函數節流與函數防抖
函數節流和函數防抖是一個老生常談的話題了,兩者都是對大量頻繁重複調用代碼的一種優化方案 今天在某群和大家討論時,順便搜了一些相關博客 發現有一篇關於兩者的定義竟然寫反了。。。所以決定自己來寫一下-.-權當加深記憶了
函數節流(throttle)
正如其命名的含義,節流。 限制函數在一定時間內調用的次數。
類似的實際生活中的場景
早晚高峰的地鐵排隊。

太多的人擁擠到站台上,大家都想搭上這班車,人擠人之間,難免會出現一些問題。 所以在很多地鐵站,高峰期會設置很多層的屏障,來增加你進站的時間,從而減少站台的壓力。

在程序中的實踐
同理,代入程序中,我們可以通過限制函數調用的頻率,來抑制資源的消耗。 比如我們要實現一個元素拖拽的效果,我們是可以在每次move
事件中都進行重繪DOM,但是這樣做,程序的開銷是非常大的。 所以在這裡我們可以用到函數節流
的方法,來減少重繪的次數:
// 普通方案 $dragable.addEventListener('mousemove', () => { console.log('trigger') }) // 函數節流的實現方案 let throttleIdentify = 0 $dragable.addEventListener('mousemove', () => { if (throttleIdentify) return throttleIdentify = setTimeout(() => throttleIdentify = 0, 500) console.log('trigger') })
這樣來做的話,在拖動的過程中,能保證500ms內,只會重繪一次DOM。 在同時監聽了mouseover
後,兩者最終的效果是一致的,但是在拖動的過程中,函數節流
版觸發事件的次數會減少很多,遂消耗更少的資源。
通用的函數節流實現
/** * 函數節流的實現 * @param {Function} func 要實現函數節流的原函數 * @param {Number} interval 節流的間隔 * @return {Function} 添加節流功能的函數 */ function throttle (func, interval) { let identify = 0 return (...args) => { if (identify) return identify = setTimeout(() => identify = 0, interval) func.apply(this, args) } }
類似函數節流的操作
平時開發中經常會做的ajax
請求獲取數據,這裡可以用到類似函數節流的操作。 在我們發送一個請求到後台時,當返回的數據還沒有接收到時,我們會添加一個標識,來表明當前有一個請求正在被處理,如果這時用戶再觸發ajax
請求,則會直接跳過本次函數的執行。 同樣的還有滑動加載更多數據,如果不添加類似的限制,可能會導致發送多條請求,渲染重複數據。
我曾經在某軟件里遇到過-.-連續點擊登錄按鈕數十次,結果連着給我彈了三次密碼錯誤,隨後告訴我輸入密碼錯誤超過三次,您的賬號已被鎖定。
函數節流的定義:限制函數在一定時間內調用的次數
函數防抖(debounce)
是函數在特定的時間內不被再調用後執行。
實際的例子
還是拿城市交通工具來說事兒。。 坐公交時,到站了,是由司機來操作車門的開合的。 當司機準備離站時,關閉車門,這是突然跑過來一人,司機只好再將車門打開,讓人上來。
又或者,乘坐升降電梯時,電梯門關閉後,外邊跑來一人又將電梯門按開。 這兩件事兒都是因為關門
這一個事件處理太快導致的,徒增一次開關門的消耗。
在程序中的實踐
監聽窗口大小重繪的操作。
在用戶拖拽窗口時,一直在改變窗口的大小,如果我們在resize
事件中進行一些操作,消耗將是巨大的。 而且大多數可能是無意義的執行,因為用戶還處於拖拽的過程中。 所以我們可以用函數防抖
來優化相關的處理
// 普通方案 window.addEventListener('resize', () => { console.log('trigger') }) // 函數防抖方案 let debounceIdentify = 0 window.addEventListener('resize', () => { debounceIdentify && clearTimeout(debounceIdentify) debounceIdentify = setTimeout(() => { console.log('trigger') }, 300) })
我們在resize
事件中添加了一個300ms的延遲執行邏輯。 並且在每次事件觸發時,都會重新計時,這樣做也就可以保證,函數的執行肯定是在距離上次resize
事件被觸發的300ms後。 兩次resize
事件間隔小於300ms的都會被忽略,這樣就節省了很多無意義的事件觸發。
輸入框的輸入聯想
幾乎所有的搜索引擎都會對你輸入的文字進行預判,並在下方推薦相關的結果。 但是這個聯想意味着我們需要將當前用戶所輸入的文本傳遞到後端,並獲取返回數據,展示在頁面中。 如果遇到打字速度快的人,比如260字母/分鐘的我,在一小段時間內,會連續發送大量的ajax
請求到後端。 並且當前邊的數據返回過來後,其實已經失去了展示的意義,因為用戶可能從you
輸入到了young
,這兩個單詞相關的結果肯定是不一樣的。 所以我們就可以在監聽用戶輸入的事件那裡做函數防抖的處理,在XXX秒後發送聯想搜索的ajax
請求。
通用的函數防抖實現
/** * 函數防抖的實現 * @param {Function} func 要實現函數節流的原函數 * @param {Number} delay 結束的延遲時間 * @return {Function} 添加節流功能的函數 */ function debounce (func, delay) { let debounceIdentify = 0 return (...args) => { debounceIdentify && clearTimeout(debounceIdentify) debounceIdentify = setTimeout(() => { debounceIdentify = 0 func.apply(this, args) }, delay) } }
類似函數防抖的操作
在一些與用戶的交互上,比如提交表單後,一般都會顯示一個loading框來提示用戶,他提交的表單正在處理中。 但是發送表單請求後就顯示loading是一件很不友好的事情,因為請求可能在幾十毫秒內就會得到響應。 這樣在用戶看來就是頁面中閃過一團黑色,所以可以在提交表單後添加一個延遲函數,在XXX秒後再顯示loading框。 這樣在快速響應的場景下,用戶是不會看到一閃而過的loading框,當然,一定要記得在接收到數據後去clearTimeout
let identify = setTimeout(showLoadingModal, 500) fetch('XXX').then(res => { // doing something // clear timer clearTimeout(identify) })
函數防抖的定義:函數在特定的時間內不被再調用後執行
總結
函數節流、函數防抖 兩者都是用來解決代碼短時間內大量重複調用的方案。 當然,也是各有利弊。 在實際開發中,兩者的界定也很模糊。 比如搜索關鍵字聯想,用節流或者防抖都可以來做,拖拽DOM、監聽resize等等,這兩個都是可以來實現的。
兩者的區別在於: 函數節流在一定時間內肯定會觸發多次,但是最後不一定會觸發 函數防抖可能僅在最後觸發一次
記住上邊這兩句,我覺得遇到類似需要進行優化的場景,應該就能夠知道該用哪個了。