高頻:手寫一個節(jié)流函數(shù) Throttle
debounce 與 throttle 是開發(fā)中常用的高階函數(shù),作用都是為了防止函數(shù)被高頻調(diào)用,換句話說就是,用來控制某個函數(shù)在一定時間內(nèi)執(zhí)行多少次。
使用場景
比如綁定響應(yīng)鼠標移動、窗口大小調(diào)整、滾屏等事件時,綁定的函數(shù)觸發(fā)的頻率會很頻繁。若稍處理函數(shù)微復(fù)雜,需要較多的運算執(zhí)行時間和資源,往往會出現(xiàn)延遲,甚至導(dǎo)致假死或者卡頓感。為了優(yōu)化性能,這時就很有必要使用 debounce 或 throttle 了。
debounce 與 throttle 區(qū)別
防抖 (debounce) :多次觸發(fā),只在最后一次觸發(fā)時,執(zhí)行目標函數(shù)。
節(jié)流(throttle):限制目標函數(shù)調(diào)用的頻率,比如:1s內(nèi)不能調(diào)用2次。
手寫一個 throttle
實現(xiàn)方案有以下兩種:
- 第一種是用時間戳來判斷是否已到執(zhí)行時間,記錄上次執(zhí)行的時間戳,然后每次觸發(fā)事件執(zhí)行回調(diào),回調(diào)中判斷當前時間戳距離上次執(zhí)行時間戳的間隔是否已經(jīng)達到時間差(Xms) ,如果是則執(zhí)行,并更新上次執(zhí)行的時間戳,如此循環(huán)。
- 第二種方法是使用定時器,比如當 scroll 事件剛觸發(fā)時,打印一個 hello world,然后設(shè)置個 1000ms 的定時器,此后每次觸發(fā) scroll 事件觸發(fā)回調(diào),如果已經(jīng)存在定時器,則回調(diào)不執(zhí)行方法,直到定時器觸發(fā),handler 被清除,然后重新設(shè)置定時器。
這里我們采用第一種方案來實現(xiàn),通過閉包保存一個 previous 變量,每次觸發(fā) throttle 函數(shù)時判斷當前時間和 previous 的時間差,如果這段時間差小于等待時間,那就忽略本次事件觸發(fā)。如果大于等待時間就把 previous 設(shè)置為當前時間并執(zhí)行函數(shù) fn。
我們來一步步實現(xiàn),首先實現(xiàn)用閉包保存 previous 變量。
- const throttle = (fn, wait) => {
- // 上一次執(zhí)行該函數(shù)的時間
- let previous = 0
- return function(...args) {
- console.log(previous)
- ...
- }
- }
執(zhí)行 throttle 函數(shù)后會返回一個新的 function ,我們命名為 betterFn 。
- const betterFn = function(...args) {
- console.log(previous)
- ...
- }
betterFn 函數(shù)中可以獲取到 previous 變量值也可以修改,在回調(diào)監(jiān)聽或事件觸發(fā)時就會執(zhí)行 betterFn ,即 betterFn(),所以在這個新函數(shù)內(nèi)判斷當前時間和 previous 的時間差即可。
- const betterFn = function(...args) {
- let now = +new Date();
- if (now - previous > wait) {
- previous = now
- // 執(zhí)行 fn 函數(shù)
- fn.apply(this, args)
- }
- }
結(jié)合上面兩段代碼就實現(xiàn)了節(jié)流函數(shù),所以完整的實現(xiàn)如下。
- // fn 是需要執(zhí)行的函數(shù)
- // wait 是時間間隔
- const throttle = (fn, wait = 50) => {
- // 上一次執(zhí)行 fn 的時間
- let previous = 0
- // 將 throttle 處理結(jié)果當作函數(shù)返回
- return function(...args) {
- // 獲取當前時間,轉(zhuǎn)換成時間戳,單位毫秒
- let now = +new Date()
- // 將當前時間和上一次執(zhí)行函數(shù)的時間進行對比
- // 大于等待時間就把 previous 設(shè)置為當前時間并執(zhí)行函數(shù) fn
- if (now - previous > wait) {
- previous = now
- fn.apply(this, args)
- }
- }
- }
- // DEMO
- // 執(zhí)行 throttle 函數(shù)返回新函數(shù)
- const betterFn = throttle(() => console.log('fn 函數(shù)執(zhí)行了'), 1000)
- // 每 10 毫秒執(zhí)行一次 betterFn 函數(shù),但是只有時間差大于 1000 時才會執(zhí)行 fn
- setInterval(betterFn, 10)