如何全面出色的回答面試官防抖與節流提問?

藍布偶發表於2020-01-05

節流與防抖這兩個概念大家並不陌生,面時高頻題,可是你是否真正瞭解兩者的真正區別?是否能夠在實際開發中知道什麼時候該用防抖?什麼用節流?

閱讀本文你將收穫:

  • 清晰認識防抖與節流之間的區別,並能夠正確的應用與實際開發中
  • 多樣的程式碼實現
  • 閉包的特性的應用

什麼是防抖

debounce(fn, threshhold)

技術最終都要服務於社會,任何脫離業務(社會)實際的都是耍流氓,防抖當然也不例外,實際生活中對於拍照(人工防抖,不是智慧那種:joy:),如果你在自拍,你肯定不會在鏡頭沒穩定之前,按下快門吧(PS:如果你想要這種模糊效果當我沒說:no_mouth:),也就是鏡頭不穩(手抖)你不會按下快門,如果你感覺穩了,才會按下快門,類似:

  1. 防抖函式debounce的功能就相當於幫你判斷什麼時候該按下快門
  2. fn相當於快門
  3. threshhold(閾值)就相當於人體感知穩定的需要經歷的時間閾值
  4. 只有穩定之後才會按下快門即執行fn,也就是說一旦間隔threshhold有一次抖動都會重新判斷穩定
  5. 如果threshhold間隔內一直穩定不下來,第一次觸發threshholdms之後fn不會被執行,同理一直不穩定,fn永遠不會被執行(假如死迴圈)
// fn => 2
function debounce(fn, threshhold){ // 1
    if(!fn instanceof Function) {
        throw new TypeError('Expected a function')
    }
    let timer = null;
    return function () {
        clearTimeout(timer); // 3
        timer = setTimeout(() => { 
            fn.apply(this) // 4 
        },threshhold)
    };
}
複製程式碼

程式碼淺析:debounce就相當於幫你判斷是什麼時候該按下快門,要執行的fn相當於快門,人體的感知穩定時間閾值為threshhold,如果連續兩次呼叫(對應拍照抖動)小於threshhold,那麼肯定要重新設定穩定間隔的起始點也就是重置clearTimeout(timer),當然如果兩次間隔超過threshhold,重置已經無法影響了已經發生的呼叫了,最後定時器執行fn.apply(this)就是手終於不抖可以按下快門啦:joy:

什麼是節流

throttle(fn, threshhold)

實際生活中,節流這一概念其實生活中有很多例子,比如這快過年了,火車站考慮到大家的安全,對進站進行節流(官方應該叫限流,其實表達都是同一個意思)因為單位時間內車站的接待(容納)人數是有限的,還有大家更加熟悉的例子,王者榮耀或者英雄聯盟這類moba遊戲,都有攻速上限(攻速2.5),換句話說哦假設程式設定了英雄一秒最多A五下,那麼即使你手速再快,1s內也A不出第六下,通過以上例子我們可以得出:

  1. threshhold間隔內函式fn無論觸發多少次,第一次觸發到threshholdms後都是隻執行一次
  2. 第二次觸發距離第一次時間超過threshhold,則第二次會立即執行

根據這兩點,有兩種實現方式

第一種

function throttle(fn, threshhold) {
    if(!fn instanceof Function) {
        throw new TypeError('Expected a function')
    }
    let limited = false;  // 節流閥標誌位
    let start = Date.now();
    threshhold = threshhold || 500
    return function (...args) {
        let current = Date.now();
        limited = limited && current- start < threshhold
        if(!limited) {
                fn.apply(this,args);
                limited = true;
                start = Date.now();
        }
    }
}
複製程式碼

程式碼淺析:通過limited節流閥標誌位模擬當前是否需要節流(限流),第一次預設false即首次不限流(車站為空的:joy:),限流之後(limited = true)且只有兩次時間間隔(current- start)超過threshhold,才會除去限制,呼叫fn即車站讓旅客進站進入新的週期重置開始時間start

第二種

function throttle2(fun, threshhold) {
    if(!fun instanceof Function) {
        throw new TypeError('Expected a function')
    }
    let limited = false; // 節流閥標誌位
    let timer = null;
    let start = Date.now();
    threshhold = threshhold || 500
    return function (...args) {
        let current = Date.now();
        limited = limited && current- start < threshhold
        if (limited) {
            clearTimeout(timer)
            timer = setTimeout(() => {
                limited = true
                start = Date.now()
                fun.apply(this, args)
            }, threshhold)
        }else {
            limited = true
            start = Date.now();
            fun.apply(this,args)
        }
    }
}
複製程式碼

程式碼淺析:第二種使用了setTimeout定時器的方式,多加了如果最後一次觸發距離上一次呼叫fn小於threshhold則這次設定的定時器回撥將會在下一個threshhold週期內執行,所以這種方式觸發多次fn總共會執行兩次,只是第二次會在下一個threshhold週期內執行

節流兩種方式對比

  • 第一種,一個threshhold間隔內多次促發,fn只會被執行一次,最後一次並不會進入下一個週期執行,比如連續1秒內平A了5次超過限度(節流)5次,第六次並不會說下一秒自動平A,而是直接捨去
  • 第二種,一個threshhold間隔內多次促發,fn總共會執行兩次,注意第二次會進入下一個threshhold週期執行

兩者比較

相同點:

  • 其實本質上都是為了節省程式的效能(防止高頻函式呼叫)
  • 藉助了閉包的特性來快取變數(狀態)
  • 都可以使用setTimeout實現

區別:

  • 使用防抖,可能n個threshhold時間間隔之後fn也沒執行,但是使用節流觸發的threshhold間隔內有且只執行一次
  • 同樣threshhold間隔內連續觸發,防抖只執行一次,而節流會執行兩次,只是在不同的threshhold週期內
  • 側重點不同,防抖側重於穩定只能執行一次,而節流強調限週期內次數,即執行頻率,不限制所有時間內的總次數

應用場景

防抖:

  • 一些表單元素的校驗,如手機號,郵箱,使用者名稱等
  • 部分搜尋功能的聯想結果實現

節流:

  • 一些滑鼠的跟隨動畫實現
  • scroll,resize, touchmove, mousemove等極易持續性促發事件的相關動畫問題,降低頻率

總結

無論什麼技術都有他擅長的地方,技術與實現的優與劣不能單從技術方面去考量,這樣是沒有意義的,如對於節流函式的兩種方式,他們都有適合的場景,比如你的產品需要類似遊戲內限制攻速的,顯然節流的第一種方案更合適,元芳你怎麼看?:laughing:(ps:github原始碼地址含單測)

參考連結

相關文章