JS防抖和節(jié)流

防抖和節(jié)流

相同:在不影響客戶體驗的前提下,將頻繁的回調(diào)函數(shù),進(jìn)行次數(shù)縮減.避免大量計算導(dǎo)致的頁面卡頓.

不同:防抖是將多次執(zhí)行變?yōu)樽詈笠淮螆?zhí)行,節(jié)流是將多次執(zhí)行變?yōu)樵谝?guī)定時間內(nèi)只執(zhí)行一次.

防抖

定義:

指觸發(fā)事件后在規(guī)定時間內(nèi)回調(diào)函數(shù)只能執(zhí)行一次,如果在規(guī)定時間內(nèi)觸發(fā)了該事件,則會重新開始算規(guī)定時間。

網(wǎng)上有這個比喻:函數(shù)防抖就是法師發(fā)技能的時候要讀條,技能讀條沒完再按技能就會刷新技能,重新進(jìn)行讀條。
四個字總結(jié)就是 延時執(zhí)行

應(yīng)用場景:

兩個條件:
1. 如果客戶連續(xù)的操作會導(dǎo)致頻繁的事件回調(diào)(可能引起頁面卡頓).
2. 客戶只關(guān)心"最后一次"操作(也可以理解為停止連續(xù)操作后)所返回的結(jié)果.

例如:

  • 輸入搜索聯(lián)想,用戶在不斷輸入值時,用防抖來節(jié)約請求資源。
  • 按鈕點(diǎn)擊:收藏,點(diǎn)贊,心標(biāo)等

原理:

通過定時器將回調(diào)函數(shù)進(jìn)行延時,如果在規(guī)定時間內(nèi)繼續(xù)回調(diào),發(fā)現(xiàn)存在之前的定時器,則將該定時器清除,并重新設(shè)置定時器.這里有個細(xì)節(jié),就是后面所有的回調(diào)函數(shù)都要能訪問到之前設(shè)置的定時器,這時就需要用到閉包(詳見后面提到的)

兩種版本

防抖分為兩種:
1) 非立即執(zhí)行版:事件觸發(fā)->延時->執(zhí)行回調(diào)函數(shù);如果在延時中,繼續(xù)觸發(fā)事件,則會重新進(jìn)行延時.在延時結(jié)束后執(zhí)行回調(diào)函數(shù),常見例子:就是input搜索框,客戶輸完過一會就會自動搜索
2) 立即執(zhí)行版:事件觸發(fā)->執(zhí)行回調(diào)函數(shù)->延時;如果在延時中,繼續(xù)觸發(fā)事件,則會重新進(jìn)行延時.在延時結(jié)束后,并不會執(zhí)行回調(diào)函數(shù).常見例子:就是對于按鈕防點(diǎn)擊.例如點(diǎn)贊,心標(biāo),收藏等有立即反饋的按鈕.

實現(xiàn)代碼及思路:

//非立即執(zhí)行版:
//首先準(zhǔn)備我們要使用的回調(diào)函數(shù)
function shotCat (content) {
  console.log('shotCat出品,必屬精品!必須點(diǎn)贊!(滑稽)')
}

//然后準(zhǔn)備包裝函數(shù):
//1,保存定時器標(biāo)識 
/*2,返回閉包函數(shù): 
 *1)對定時器的判斷清除;
 *2)一般還需要保存函數(shù)的參數(shù)(一般就是事件返回的對象)和上下文(定時器存在this隱式丟失,詳情可以看我不知道的js上)
 */
//最后補(bǔ)充一句,這里不建議通過定義一個全局變量來替代閉包保存定時器標(biāo)識.
function debounce(fun, delay = 500) {
//let timer = null 保存定時器
  return function (args) {
    let that = this
    let _args = args
    //這里對定時器的設(shè)置有兩種方法,第一種就是將定時器保存在函數(shù)(函數(shù)也是對象)的屬性上,
    //這種寫法,很簡便,但不是很常用
    clearTimeout(fun.timer)
    fun.timer = setTimeout(function () {
      fun.call(that, _args)
    }, delay)
    //另外一種寫法就是我們比較常見的
    //if (timer) clearTimeout(timer);     相比上面的方法,這里多一個判斷
    //timer = setTimeout(function () {
      //    fun.call(that, _args)
    //}, delay)
  }
}

//接著用變量保存保存 debounce 返回的帶有延時功能的函數(shù)
let debounceShotCat = debounce(shotCat, 500)  

//最后添加事件監(jiān)聽 回調(diào)debounceShotCat 并傳入事件返回的對象
let input = document.getElementById('debounce')
input.addEventListener('keyup', function (e) {
  debounceShotCat(e.target.value)
})

//帶有立即執(zhí)行選項的防抖函數(shù):
//思路和上面的大致相同,如果是立即執(zhí)行,則定時器中不再包含回調(diào)函數(shù),而是在回調(diào)函數(shù)執(zhí)行后,僅起到延時和重置定時器標(biāo)識的作用
function debounce(fun, delay = 500,immediate = true) {
  let timer = null //保存定時器
  return function (args) {
    let that = this
    let _args = args
    if (timer) clearTimeout(timer);  //不管是否立即執(zhí)行都需要首先清空定時器
    if (immediate) {
      if ( !timer) fun.apply(that, _args)  //如果定時器不存在,則說明延時已過,可以立即執(zhí)行函數(shù)
      //不管上一個延時是否完成,都需要重置定時器
      timer = setTimeout(function(){
        timer = null; //到時間后,定時器自動設(shè)為null,不僅方便判斷定時器狀態(tài)還能避免內(nèi)存泄露
      }, delay)
    }else {
      //如果是非立即執(zhí)行版,則重新設(shè)定定時器,并將回調(diào)函數(shù)放入其中
      timer = setTimeout(function(){
        fun.call(that, _args)
      }, delay);
    }
  }
}

節(jié)流

定義:

當(dāng)持續(xù)觸發(fā)事件時,在規(guī)定時間段內(nèi)只能調(diào)用一次回調(diào)函數(shù)。如果在規(guī)定時間內(nèi)又觸發(fā)了該事件,則什么也不做,也不會重置定時器.

與防抖比較:

防抖是將多次執(zhí)行變?yōu)樽詈笠淮螆?zhí)行,節(jié)流是將多次執(zhí)行變?yōu)樵谝?guī)定時間內(nèi)只執(zhí)行一次.一般不會重置定時器. 即不會if (timer) clearTimeout(timer);(時間戳+定時器版除外)

應(yīng)用場景:

兩個條件:
1. 客戶連續(xù)頻繁地觸發(fā)事件
2. 客戶不再只關(guān)心"最后一次"操作后的結(jié)果反饋,而是在操作過程中持續(xù)的反饋.

例如:

  • 鼠標(biāo)不斷點(diǎn)擊觸發(fā),點(diǎn)擊事件在規(guī)定時間內(nèi)只觸發(fā)一次(單位時間內(nèi)只觸發(fā)一次)
  • 監(jiān)聽滾動事件,比如是否滑到底部自動加載更多,用throttle來判斷

注意 :何為連續(xù)頻繁地觸發(fā)事件,就是事件觸發(fā)的時間間隔至少是要比規(guī)定的時間要短.

原理:

節(jié)流有兩種實現(xiàn)方式

    1. 時間戳方式:通過閉包保存上一次的時間戳,然后與事件觸發(fā)的時間戳比較.如果大于規(guī)定時間,則執(zhí)行回調(diào).否則就什么都不處理.
      特點(diǎn): 一般第一次會立即執(zhí)行,之后連續(xù)頻繁地觸發(fā)事件,也是超過了規(guī)定時間才會執(zhí)行一次。最后一次觸發(fā)事件,也不會執(zhí)行(說明:如果你最后一次觸發(fā)時間大于規(guī)定時間,這樣就算不上連續(xù)頻繁觸發(fā)了).
    1. 定時器方式:原理與防抖類似.通過閉包保存上一次定時器狀態(tài).然后事件觸發(fā)時,如果定時器為null(即代表此時間隔已經(jīng)大于規(guī)定時間),則設(shè)置新的定時器.到時間后執(zhí)行回調(diào)函數(shù),并將定時器置為null.
      特點(diǎn): 當(dāng)?shù)谝淮斡|發(fā)事件時,不會立即執(zhí)行函數(shù),到了規(guī)定時間后才會執(zhí)行。 之后連續(xù)頻繁地觸發(fā)事件,也是到了規(guī)定時間才會執(zhí)行一次(因為定時器)。當(dāng)最后一次停止觸發(fā)后,由于定時器的延時,還會執(zhí)行一次回調(diào)函數(shù)(那也是上一次成功成功觸發(fā)執(zhí)行的回調(diào),而不是你最后一次觸發(fā)產(chǎn)生的)。一句話總結(jié)就是延時回調(diào),你能看到的回調(diào)都是上次成功觸發(fā)產(chǎn)生的,而不是你此刻觸發(fā)產(chǎn)生的.

說明: 這兩者最大的區(qū)別:是時間戳版的函數(shù)觸發(fā)是在規(guī)定時間開始的時候,而定時器版的函數(shù)觸發(fā)是在規(guī)定時間結(jié)束的時候。其他差異可以看我加粗的字. 具體理解請結(jié)合后面的代碼實例,

實現(xiàn)代碼及思路:

//時間戳版:
//這里fun指的就是回調(diào)函數(shù),我就不寫出來了
function throttle(fun, delay = 500) {
  let previous = 0;  //記錄上一次觸發(fā)的時間戳.這里初始設(shè)為0,是為了確保第一次觸發(fā)產(chǎn)生回調(diào)
  return function(args) {
    let now = Date.now(); //記錄此刻觸發(fā)時的時間戳
    let that = this;
    let _args = args;
    if (now - previous > delay) {  //如果時間差大于規(guī)定時間,則觸發(fā)
      fun.apply(that, _args);
      previous = now;
    }
  }
}

//定時器版:
function throttle(fun, delay = 500) {
  let timer;
  return function(args) {
    let that = this;
    let _args = args;
    if (!timer) {  //如果定時器不存在,則設(shè)置新的定時器,到時后,才執(zhí)行回調(diào),并將定時器設(shè)為null
      timer = setTimeout(function(){
        timer = null;
        fun.apply(that, _args)
      }, delay)
    }
  }
}

//時間戳+定時器版: 實現(xiàn)第一次觸發(fā)可以立即響應(yīng),結(jié)束觸發(fā)后也能有響應(yīng) (該版才是最符合實際工作需求)
//該版主體思路還是時間戳版,定時器的作用僅僅是執(zhí)行最后一次回調(diào)
function throttle(fun, delay = 500) {
  let timer = null;
  let previous = 0;
  return function(args) {
    let now = Date.now();
    let remaining = delay - (now - previous); //距離規(guī)定時間,還剩多少時間
    let that = this;
    let _args = args;
    clearTimeout(timer);  //清除之前設(shè)置的定時器
    if (remaining <= 0) {
      fun.apply(that, _args);
      previous = Date.now();
    } else {
      timer = setTimeout(function(){
        fun.apply(that, _args)
      }, remaining); //因為上面添加的clearTimeout.實際這個定時器只有最后一次才會執(zhí)行
    }
  }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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