防抖(Debouncing)、節(jié)流(Throttling)和 rAF(requestAnimationFrame)的原理

網(wǎng)頁生成的時候,至少會渲染(Layout+Paint)一次。用戶訪問的過程中,還會不斷重新的重排(reflow)和重繪(repaint)。用戶 scroll 和 resize 行為(即是滑動頁面和改變窗口大?。е马撁娌粩嗟闹匦落秩尽croll 事件本身會觸發(fā)頁面的重新渲染,同時 scroll 事件的 handler 又會被高頻度的觸發(fā), 因此事件的 handler 內(nèi)部不應該有復雜操作,例如 DOM 操作就不應該放在事件處理中。針對此類高頻度觸發(fā)事件問題(例如頁面 scroll ,屏幕 resize,監(jiān)聽用戶輸入等),下面介紹兩種常用的解決方法防抖節(jié)流。

感謝博主分享 :
ChokCoco
Alon's Blog

防抖(Debouncing)

防抖技術(shù)即是可以把多個順序地調(diào)用合并成一次,也就是在一定時間內(nèi),規(guī)定事件被觸發(fā)的次數(shù)。

window.addEventListener('scroll',debounce(realFunc,500))
     //優(yōu)化滾動效果防抖動
     function debounce(fn,time){ 
        var timeout = null;
        return function(){
           clearTimeout(timeout)
           timeout = setTimeout(fn,time) 
        }
     }
     function realFunc(){
       console.log('this is real callback function')
     }
           
     }

原理:

  • 首先,我們?yōu)閟croll事件綁定處理函數(shù),這時debounce函數(shù)會立即調(diào)用,
    因此給scroll事件綁定的函數(shù)實際上是debounce內(nèi)部返回的函數(shù)

  • 每一次事件被觸發(fā),都會清除當前的 timer 然后重新設(shè)置超時調(diào)用。
    這就會導致每一次高頻事件都會取消前一次的超時調(diào)用,導致事件處理程序不能被觸發(fā)

  • 只有當高頻事件停止,最后一次事件觸發(fā)的超時調(diào)用才能在delay時間后執(zhí)行
    繼續(xù)優(yōu)化的寫法:不希望非要等到事件停止觸發(fā)后才執(zhí)行,我希望立刻執(zhí)行函數(shù),然后等到停止觸發(fā) n 秒后,才可以重新觸發(fā)執(zhí)行。

window.addEventListener('scroll',debounce(realFunc,500,true))
     //優(yōu)化滾動效果防抖動
     function debounce(fn,time,immediate){ 
        var timeout = null;
        return function(){
            if(timeout){
              clearTimeout(timeout)
            }
            if(immediate){
              //根據(jù)距離上次觸發(fā)操作的時間是否到達delay來決定是否要現(xiàn)在執(zhí)行函數(shù)
              var doNow = !timeout
              //每一次都重新設(shè)置timer,就是要保證每一次執(zhí)行的至少delay秒后才可以執(zhí)行
              timeout = setTimeout(function(){
                timeout = null
              },time)
              //立即執(zhí)行
              if(doNow) fn()
            }else{
              timeout = setTimeout(fn,time) 
            }
           console.log('after'+timeout)
        }
     }
     function realFunc(){
       console.log('this is real callback function')
     }
           
     }

Debounce 實例 : 調(diào)整大小的例子 調(diào)整桌面瀏覽器窗口大小的時候,會觸發(fā)很多次 resize 事件。
oninput實時搜索的demo

節(jié)流(Throttling)

防抖函數(shù)確實不錯,但是也存在問題,譬如圖片的懶加載,我希望在下滑過程中圖片不斷的被加載出來,而不是只有當我停止下滑時候,圖片才被加載出來。又或者下滑時候的數(shù)據(jù)的 ajax 請求加載也是同理。
這個時候,我們希望即使頁面在不斷被滾動,但是滾動 handler 也可以以一定的頻率被觸發(fā)(譬如 250ms 觸發(fā)一次),這類場景,就要用到另一種技巧,稱為節(jié)流函數(shù)(throttling)。
節(jié)流函數(shù),只允許一個函數(shù)在 X 毫秒內(nèi)執(zhí)行一次。
與防抖相比,節(jié)流函數(shù)最主要的不同在于它保證在 X 毫秒內(nèi)至少執(zhí)行一次我們希望觸發(fā)的事件 handler。
與防抖相比,節(jié)流函數(shù)多了一個 mustRun 屬性,代表 mustRun 毫秒內(nèi),必然會觸發(fā)一次 handler ,同樣是利用定時器,看看簡單的示例:

window.addEventListener('scroll',throttle(realFunc,500,1000))
     //優(yōu)化滾動效果節(jié)流
     function throttle(fn,time){ 
        var timeout,
            startTime = new Date()
        return function(){
           var context = this,
               args = arguments,
               curTime = new Date(),
               remaining = time - (curTime - startTime);
           clearTimeout(timeout)
        if(remaining <=0){
              fn.apply(context,args)
              startTime = curTime
           }else{
               timeout = setTimeout(fn,remaining) 
           }

        }
     }
     function realFunc(){
       console.log('this is real callback function')
     }

節(jié)流閥實例 : 無限滾動 用戶向下滾動無限滾動頁面,需要檢查滾動位置距底部多遠,如果鄰近底部了,我們可以發(fā) AJAX 請求獲取更多的數(shù)據(jù)插入到頁面中。
下拉加載demo

使用 rAF(requestAnimationFrame)觸發(fā)滾動事件

上面介紹的抖動與節(jié)流實現(xiàn)的方式都是借助了定時器 setTimeout ,但是如果頁面只需要兼容高版本瀏覽器或應用在移動端,又或者頁面需要追求高精度的效果,那么可以使用瀏覽器的原生方法 rAF(requestAnimationFrame)。
簡單而言,使用 requestAnimationFrame 來觸發(fā)滾動事件,相當于上面的:

throttle(func, xx, 1000/60) //xx 代表 xx ms內(nèi)不會重復觸發(fā)事件 handler
滑動過程中嘗試使用 pointer-events: none 禁止鼠標事件/不要去修改樣式
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容