前端面試查漏補缺--(一) 防抖和節流

shotCat發表於2019-02-22

前言

本系列最開始是為了自己面試準備的.後來發現整理越來越多,差不多有十二萬字元,最後決定還是分享出來給大家.

為了分享整理出來,花費了自己大量的時間,起碼是隻自己用的三倍時間.如果喜歡的話,歡迎收藏,關注我!謝謝!

文章連結

合集篇:

前端面試查漏補缺--Index篇(12萬字元合集) 包含目前已寫好的系列其他十幾篇文章.後續新增值文章不會再在每篇新增連結,強烈建議議點贊,關注合集篇!!!!,謝謝!~

後續更新計劃

後續還會繼續新增設計模式,前端工程化,專案流程,部署,閉環,vue常考知識點 等內容.如果覺得內容不錯的話歡迎收藏,關注我!謝謝!

求一份內推

目前本人也在準備跳槽,希望各位大佬和HR小姐姐可以內推一份靠譜的武漢 前端崗位!郵箱:bupabuku@foxmail.com.謝謝啦!~

防抖和節流

相同:在不影響客戶體驗的前提下,將頻繁的回撥函式,進行次數縮減.避免大量計算導致的頁面卡頓.

不同:防抖是將多次執行變為最後一次執行,節流是將多次執行變為在規定時間內只執行一次.

防抖

定義:

指觸發事件後在規定時間內回撥函式只能執行一次,如果在規定時間內觸發了該事件,則會重新開始算規定時間。

網上有這個比喻:函式防抖就是法師發技能的時候要讀條,技能讀條沒完再按技能就會重新整理技能,重新進行讀條。

四個字總結就是 延時執行

應用場景:

兩個條件:
1,如果客戶連續的操作會導致頻繁的事件回撥(可能引起頁面卡頓).
2,客戶只關心"最後一次"操作(也可以理解為停止連續操作後)所返回的結果.
例如:

  • 輸入搜尋聯想,使用者在不斷輸入值時,用防抖來節約請求資源。
  • 按鈕點選:收藏,點贊,心標等

原理:

通過定時器將回撥函式進行延時.如果在規定時間內繼續回撥,發現存在之前的定時器,則將該定時器清除,並重新設定定時器.這裡有個細節,就是後面所有的回撥函式都要能訪問到之前設定的定時器,這時就需要用到閉包(詳見後面提到的)

兩種版本

防抖分為兩種:

  • 1)非立即執行版:事件觸發->延時->執行回撥函式;如果在延時中,繼續觸發事件,則會重新進行延時.在延時結束後執行回撥函式.常見例子:就是input搜尋框,客戶輸完過一會就會自動搜尋
  • 2)立即執行版:事件觸發->執行回撥函式->延時;如果在延時中,繼續觸發事件,則會重新進行延時.在延時結束後,並不會執行回撥函式.常見例子:就是對於按鈕防點選.例如點贊,心標,收藏等有立即反饋的按鈕.

實現程式碼及思路:

//非立即執行版:
//首先準備我們要使用的回撥函式
function shotCat (content) {
  console.log('shotCat出品,必屬精品!必須點贊!(滑稽)')
}

//然後準備包裝函式:
//1,儲存定時器標識 
//2,返回閉包函式: 1)對定時器的判斷清除;2)一般還需要儲存函式的引數(一般就是事件返回的物件)和上下文(定時器存在this隱式丟失,詳情可以看我不知道的js上)
//最後補充一句,這裡不建議通過定義一個全域性變數來替代閉包儲存定時器標識.
function debounce(fun, delay = 500) {
//let timer = null 儲存定時器
    return function (args) {
        let that = this
        let _args = args
		//這裡對定時器的設定有兩種方法,第一種就是將定時器儲存在函式(函式也是物件)的屬性上,
		//這種寫法,很簡便,但不是很常用
        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 返回的帶有延時功能的函式
let debounceShotCat = debounce(shotCat, 500)  

//最後新增事件監聽 回撥debounceShotCat 並傳入事件返回的物件
let input = document.getElementById('debounce')
input.addEventListener('keyup', function (e) {
        debounceShotCat(e.target.value)
})



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

複製程式碼

節流

定義:

當持續觸發事件時,在規定時間段內只能呼叫一次回撥函式。如果在規定時間內觸發了該事件,則什麼也不做,也不會重置定時器.

與防抖比較:

防抖是將多次執行變為最後一次執行,節流是將多次執行變為在規定時間內只執行一次.一般不會重置定時器. 即不會if (timer) clearTimeout(timer);(時間戳+定時器版除外)

應用場景:

兩個條件:
1,客戶連續頻繁地觸發事件
2,客戶不再只關心"最後一次"操作後的結果反饋.而是在操作過程中持續的反饋.
例如:

  • 滑鼠不斷點選觸發,點選事件在規定時間內只觸發一次(單位時間內只觸發一次)
  • 監聽滾動事件,比如是否滑到底部自動載入更多,用throttle來判斷

注意 :何為連續頻繁地觸發事件,就是事件觸發的時間間隔至少是要比規定的時間要短.

原理:

節流有兩種實現方式

    1. 時間戳方式:通過閉包儲存上一次的時間戳,然後與事件觸發的時間戳比較.如果大於規定時間,則執行回撥.否則就什麼都不處理.
    • 特點:一般第一次會立即執行,之後連續頻繁地觸發事件,也是超過了規定時間才會執行一次。最後一次觸發事件,也不會執行(說明:如果你最後一次觸發時間大於規定時間,這樣就算不上連續頻繁觸發了).
    1. 定時器方式:原理與防抖類似.通過閉包儲存上一次定時器狀態.然後事件觸發時,如果定時器為null(即代表此時間隔已經大於規定時間),則設定新的定時器.到時間後執行回撥函式,並將定時器置為null.
    • 特點:當第一次觸發事件時,不會立即執行函式,到了規定時間後才會執行。 之後連續頻繁地觸發事件,也是到了規定時間才會執行一次(因為定時器)。當最後一次停止觸發後,由於定時器的延時,還會執行一次回撥函式(那也是上一次成功成功觸發執行的回撥,而不是你最後一次觸發產生的)。一句話總結就是延時回撥,你能看到的回撥都是上次成功觸發產生的,而不是你此刻觸發產生的.
  • 說明: 這兩者最大的區別:是時間戳版的函式觸發是在規定時間開始的時候,而定時器版的函式觸發是在規定時間結束的時候。 其他差異可以看我加粗的字. 具體理解請結合後面的程式碼例項,

實現程式碼及思路:

//時間戳版:
//這裡fun指的就是回撥函式,我就不寫出來了
function throttle(fun, delay = 500) {
    let previous = 0;  //記錄上一次觸發的時間戳.這裡初始設為0,是為了確保第一次觸發產生回撥
    return function(args) {
        let now = Date.now(); //記錄此刻觸發時的時間戳
        let that = this;
        let _args = args;
        if (now - previous > delay) {  //如果時間差大於規定時間,則觸發
            fun.apply(that, _args);
            previous = now;
        }
    }
}


//定時器版:
function throttle(fun, delay = 500) {
    let timer;
    return function(args) {
        let that = this;
        let _args = args;
        if (!timer) {  //如果定時器不存在,則設定新的定時器,到時後,才執行回撥,並將定時器設為null
            timer = setTimeout(function(){
                timer = null;
                fun.apply(that, _args)
            }, delay)
        }

    }
}




//時間戳+定時器版: 實現第一次觸發可以立即響應,結束觸發後也能有響應 (該版才是最符合實際工作需求)
//該版主體思路還是時間戳版,定時器的作用僅僅是執行最後一次回撥
function throttle(fun, delay = 500) {
     let timer = null;
     let previous = 0;
     return function(args) {
             let now = Date.now();
             let remaining = delay - (now - previous); //距離規定時間,還剩多少時間
             let that = this;
             let _args = args;
             clearTimeout(timer);  //清除之前設定的定時器
              if (remaining <= 0) {
                    fun.apply(that, _args);
                    previous = Date.now();
              } else {
                    timer = setTimeout(function(){
                    fun.apply(that, _args)
            }, remaining); //因為上面新增的clearTimeout.實際這個定時器只有最後一次才會執行
              }
      }
}

複製程式碼

相關文章