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