js防抖&節(jié)流

防抖(debounce)

思路:在第一次觸發(fā)事件時(shí),不立即執(zhí)行函數(shù),而是給出一個(gè)期限值比如200ms,然后:

  • 如果在200ms內(nèi)沒(méi)有再次觸發(fā)滾動(dòng)事件,那么就執(zhí)行函數(shù)
  • 如果在200ms內(nèi)再次觸發(fā)滾動(dòng)事件,那么當(dāng)前的計(jì)時(shí)取消,重新開(kāi)始計(jì)時(shí)

效果:如果短時(shí)間內(nèi)大量觸發(fā)同一事件,只會(huì)執(zhí)行一次函數(shù)。
實(shí)現(xiàn):既然前面都提到了計(jì)時(shí),那實(shí)現(xiàn)的關(guān)鍵就在于setTimeOut這個(gè)函數(shù),由于還需要一個(gè)變量來(lái)保存計(jì)時(shí),考慮維護(hù)全局純凈,可以借助閉包來(lái)實(shí)現(xiàn)

function debounce(fn,delay){
    let timer = null //借助閉包
    return function() {
        if(timer){
            clearTimeout(timer) 
        }
        timer = setTimeout(fn,delay) // 簡(jiǎn)化寫(xiě)法
    }
}
// 然后是舊代碼
function showTop  () {
    var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
  console.log('滾動(dòng)條位置:' + scrollTop);
}
window.onscroll = debounce(showTop,1000)

書(shū)《JavaScript高級(jí)程序設(shè)計(jì)》中看到的樣子

var processor = {
  timeoutId: null, // 相當(dāng)于延時(shí)setTimeout的一個(gè)標(biāo)記,方便清除的時(shí)候使用

  // 實(shí)際進(jìn)行處理的方法
  // 連續(xù)觸發(fā)停止以后需要觸發(fā)的代碼
  performProcessiong: function () {
    // 實(shí)際執(zhí)行的代碼
    // 這里實(shí)際就是需要在停止觸發(fā)的時(shí)候執(zhí)行的代碼
  },

  // 初始處理調(diào)用的方法
  // 在實(shí)際需要觸發(fā)的代碼外面包一層延時(shí)clearTimeout方法,以便控制連續(xù)觸發(fā)帶來(lái)的無(wú)用調(diào)用
  process: function () {
    clearTimeout(this.timeoutId); // 先清除之前的延時(shí),并在下面重新開(kāi)始計(jì)算時(shí)間

    var that = this; // 我們需要保存作用域,因?yàn)橄旅娴膕etTimeout的作用域是在window,調(diào)用不要我們需要執(zhí)行的this.performProcessiong方法
    this.timeoutId = setTimeout(function () { // 100毫秒以后執(zhí)行performProcessiong方法
      that.performProcessiong();
    }, 100) // 如果還沒(méi)有執(zhí)行就又被觸發(fā),會(huì)根據(jù)上面的clearTimeout來(lái)清除并重新開(kāi)始計(jì)算
  }
};

// 嘗試開(kāi)始執(zhí)行
processor.process(); // 需要重新綁定在一個(gè)觸發(fā)條件里

節(jié)流(throttle)

思路:設(shè)計(jì)一種類(lèi)似控制閥門(mén)一樣定期開(kāi)放的函數(shù),也就是讓函數(shù)執(zhí)行一次后,在某個(gè)時(shí)間段內(nèi)暫時(shí)失效,過(guò)了這段時(shí)間后再重新激活(類(lèi)似于技能冷卻時(shí)間)
效果:如果短時(shí)間內(nèi)大量觸發(fā)同一事件,那么在函數(shù)執(zhí)行一次之后,該函數(shù)在指定的時(shí)間期限內(nèi)不再工作,直至過(guò)了這段時(shí)間才重新生效。
實(shí)現(xiàn):這里借助setTimeout來(lái)做一個(gè)簡(jiǎn)單的實(shí)現(xiàn),加上一個(gè)狀態(tài)位valid來(lái)表示當(dāng)前函數(shù)是否處于工作狀態(tài):

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

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

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

常用到的場(chǎng)景

  • 返回頂部問(wèn)題
  • 搜索框input事件,例如要支持輸入實(shí)時(shí)搜索可以使用節(jié)流方案(間隔一段時(shí)間就必須查詢(xún)相關(guān)內(nèi)容),或者實(shí)現(xiàn)輸入間隔大于某個(gè)值(如500ms),就當(dāng)做用戶(hù)輸入完成,然后開(kāi)始搜索,具體使用哪種方案要看業(yè)務(wù)需求。
  • 頁(yè)面resize事件,常見(jiàn)于需要做頁(yè)面適配的時(shí)候。需要根據(jù)最終呈現(xiàn)的頁(yè)面情況進(jìn)行dom渲染(這種情形一般是使用防抖,因?yàn)橹恍枰袛嘧詈笠淮蔚淖兓闆r)

原文鏈接:# 淺談js防抖和節(jié)流

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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