防抖和節(jié)流

防抖和節(jié)流嚴(yán)格意義上是屬于性能優(yōu)化方面的知識,接下來使用實際應(yīng)用場景詳細(xì)解釋防抖和節(jié)流。

案例:
在滾動條滾動到距離頂部一定距離的時候,會出現(xiàn)一個返回頂部的標(biāo)識。點擊就會執(zhí)行回到頂部的方法。

在這個案例中就會用到監(jiān)聽瀏覽器滾輪事件,事件不停的返回距離頂部的距離,那么我們可以直接寫:

  function showTop  () {
      var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
              console.log('滾動條位置:' + scrollTop);           
     }
  window.onscroll  = showTop

這里我們就會發(fā)現(xiàn)一個問題,這個函數(shù)的默認(rèn)執(zhí)行頻率太高了,瀏覽器的性能不應(yīng)該消耗在這里。

防抖(debounce)

針對這個案例以及場景,提出第一個思路:
我們在第一次執(zhí)行這個事件時給一個期限,比如200ms,

  • 如果在200ms內(nèi)沒有觸發(fā)事件,我們就只執(zhí)行一次事件
  • 如果在200ms內(nèi)再次觸發(fā)這個事件,我們就取消當(dāng)前的計時,開啟新的計時

由此這個解決方案主要是讓這個事件在一定時間內(nèi)只需要執(zhí)行一次,不會被重復(fù)的調(diào)用。
按照這個思路解決這個問題,我們就需要用到setTimeout函數(shù),還有一個變量保存計時,考慮維護(hù)全局純凈,我們使用閉包。

/*
* fn [function] 需要防抖的函數(shù)
* delay [number] 毫秒,防抖期限值
*/
function debounce(fn,delay){
    let timer = null //借助閉包
    return function() {
        if(timer){
            clearTimeout(timer) //進(jìn)入該分支語句,說明當(dāng)前正在一個計時過程中,并且又觸發(fā)了相同事件。所以要取消當(dāng)前的計時,重新開始計時
            timer = setTimeout(fn,delay) 
        }else{
            timer = setTimeout(fn,delay) // 進(jìn)入該分支說明當(dāng)前并沒有在計時,那么就開始一個計時
        }
    }
}

簡化:

/*****************************簡化后的分割線 ******************************/
function debounce(fn,delay){
    let timer = null //借助閉包
    return function() {
          if(timer){
            clearTimeout(timer) 
        }
        timer = setTimeout(fn,delay) // 簡化寫法
    }
}
// 然后是舊代碼
function showTop  () {
    var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
   console.log('滾動條位置:' + scrollTop);
}
window.onscroll = debounce(showTop,1000) // 為了方便觀察效果我們?nèi)€大點的間斷值,實際使用根據(jù)需要來配置
  • 對于短時間內(nèi)連續(xù)觸發(fā)的事件(上面的滾動事件),防抖的含義就是讓某個時間期限(如上面的1000毫秒)內(nèi),事件處理函數(shù)只執(zhí)行一次。

節(jié)流(throttle)

  • 如果在限定時間段內(nèi),不斷觸發(fā)滾動事件(比如某個用戶閑著無聊,按住滾動條不斷的拖來拖去),只要不停止觸發(fā),理論上就永遠(yuǎn)不會輸出當(dāng)前距離頂部的距離。
    但是如果產(chǎn)品同學(xué)的期望處理方案是:即使用戶不斷拖動滾動條,也能在某個時間間隔之后給出反饋呢?(此處暫且不論哪種方案更合適,既然產(chǎn)品爸爸說話了我們就先考慮怎么實現(xiàn))

方案:
我們可以設(shè)計一個類似控制閥門的開關(guān),在限定時間內(nèi)只執(zhí)行一次,然后不再工作(就像技能冷卻) 等過了這段時間再重新激活。

實現(xiàn) :
這里借助setTimeout來做一個簡單的實現(xiàn),加上一個狀態(tài)位valid來表示當(dāng)前函數(shù)是否處于工作狀態(tài):

let valid = true
    return function() {
       if(!valid){
           //休息時間 暫不接客
           return false 
       }
       // 工作時間,執(zhí)行函數(shù)并且在間隔期內(nèi)把狀態(tài)位設(shè)為無效
        valid = false
        setTimeout(() => {
            fn()
            valid = true;
        }, delay)
    }
}
/* 請注意,節(jié)流函數(shù)并不止上面這種實現(xiàn)方案,
   例如可以完全不借助setTimeout,可以把狀態(tài)位換成時間戳,然后利用時間戳差值是否大于指定間隔時間來做判定。
   也可以直接將setTimeout的返回的標(biāo)記當(dāng)做判斷條件-判斷當(dāng)前定時器是否存在,如果存在表示還在冷卻,并且在執(zhí)行fn之后消除定時器表示激活,原理都一樣
    */

// 以下照舊
function showTop  () {
    var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
      console.log('滾動條位置:' + scrollTop);
}
window.onscroll = throttle(showTop,1000) 

運行以上代碼的結(jié)果是:

  • 如果一直拖著滾動條進(jìn)行滾動,那么會以1s的時間間隔,持續(xù)輸出當(dāng)前位置和頂部的距離

其他應(yīng)用場景舉例

1.搜索框的input事件,我們就可以使用節(jié)流方案,防止輸入內(nèi)容不斷的去請求,(間隔一段時間就必須查詢相關(guān)內(nèi)容或者設(shè)置輸入間隔大于某個值【500ms】,就當(dāng)做用戶已經(jīng)輸入完成。
2.頁面resize事件,常用于做頁面的適配,需要根據(jù)最終呈現(xiàn)的頁面情況做dome渲染,此時我們可以使用防抖方案,因為只需要判斷最后一次變化情況。

----------------------------原文:https://segmentfault.com/a/1190000018428170-------------------------

個人項目經(jīng)歷:
vue 的項目有一個需求,導(dǎo)航有一個釘子效果

  • 當(dāng)釘子開啟的時候,導(dǎo)航就固定定位到頂部,
  • 當(dāng)釘子關(guān)閉的時候,滾動條滑動到大于導(dǎo)航高度時 鼠標(biāo)經(jīng)過頂部就出現(xiàn)導(dǎo)航條,移開則隱藏。
    以上效果正常寫完有個問題,就是判斷導(dǎo)航高度的時候精度判斷不準(zhǔn)確,會導(dǎo)致一些頁面滑動時出現(xiàn)抖動閃爍的問題,還有因為vue是單頁面應(yīng)用,這個效果是全局的,性能消耗大,還會和其他頁面的原生事件起沖突,這時候使用防抖,節(jié)流可以解決這個問題,最終的效果是滾動條滾動停止才觸發(fā)事件。
最后編輯于
?著作權(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ù)。

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