JavaScript中的防抖與節流-圖文版

安木夕發表於2022-12-18

image.png

01、防抖還是節流

防抖節流 目的都是避免一定時間內,大量重複的操作造成的效能損耗。因此原理也類似,都是阻止過多的事件執行,只保留一部分來執行。適用場景略有不同,也有交叉,動手練習一遍就懂了。

區別 防抖(Debounce) 節流(throttle)
描述 一定延遲時間內,連續事件只執行最後一次 一段固定時間內只執行一次
原理 只保留一個延時setTimeout()的執行器,後續新的替代舊的 判斷時間間隔,在固定間隔時間內,只執行一次。
執行次數 只執行最後一次 執行首次、最後一次,或首次+最後一次
合適場景 連續操作只需要一次的,如變更內容提交到後端 連續操作定期執行的場景:連續的UI互動,如拖拽、滾動

02、什麼是防抖(Debounce)?

按字面意思理解就是 防止抖動(Debounce /di'bauns/ 防抖動),本來只需要點選一次,結果手抖操作了很多次,重複執行就造成了額外的浪費。

image.png

?防抖函式的原理:在一定延遲時間內,連續觸發的事件合併只執行 最後 一次。技術原理是用閉包儲存一個延時執行函式setTimeout(func, delayTime)返回變數,只要延遲時間delayTime內新觸發的執行器,就會代替舊執行器。

image

?實現程式碼

/******************************   防抖函式  ******************************/
//引數func:需要防抖的函式
//引數delayTime:延時時長,單位ms
function debounce(func, delayTime) {
    //用閉包路快取延時器id
    let timer;
    return function (...args) {
        if (timer)
            clearTimeout(timer);  //清除-替換,把前浪拍死在沙灘上
        timer = setTimeout(() => {
            func.apply(this, args);
        }, delayTime);
    }
}

?適用場景

  • ✅ 提交按鈕,避免重複點選提交資料,只執行最後一次。
  • ✅ 文字框輸入的響應:如基於輸入文字服務端聯想查詢,對輸入內容的服務端驗證等,防抖就可以避免沒必要的請求,節約資源。
  • ✅ 連續觸發的事件,如視窗的resize事件、視窗的滾動scroll事件,只處理最後一次。

?使用案例:滾動瀏覽器捲軸到末尾。

  • 如果不用防抖函式,scroll事件頻繁觸發,共觸發了29次。
  • 加上防抖函式,同樣的速度移動,只觸發了最後一次。
//移動瀏覽器捲軸到末尾,無防抖
window.addEventListener('scroll', print); //執行了29次
//加上防抖,延遲300ms
window.addEventListener('scroll', debounce(print, 300)); //執行了1次
let index = 0;
function print() {
    console.log(index++);
}

03、為何要節流(throttle)?

節流(throttle)字面意思就是節約流量(throttle /ˈθrɑːtl/ 節流閥),一個小朋友一分鐘只能吃一勺飯,每分鐘餵了30勺,喂得太快要麼食物浪費了,要麼被噎到。

?節流函式的原理:一定時間內只執行一次事件,在一段時間intervalTime內,不管觸發了多少次事件(大於1)都只執行一次。

  • 因此首先需要判斷間隔時間,是否在間隔時間內。
  • 具體執行的時機,可選擇首次,也可以選擇最後一次,或者首次+最後一次。

image

?實現程式碼:三種實現方式

  • 實現1:單位時間內執行第一次(立即執行),節流後面的,基於時間間隔判斷。
  • 實現2:單位時間內執行第一次(延遲執行),節流後面的,基於延時函式setTimeout()
  • 實現3:執行首次+最後一次,節流中間的,比較綜合全面的的實現方式!
// 實現1:單位時間內執行第一次(立即執行),節流後面的
function throttle(func, intervalTime = 100) {
    let lastTime = 0;
    return (...args) => {
        let now = Date.now();
        //首次呼叫會執行
        if (now - intervalTime > lastTime) {
            func.apply(this, args);
            lastTime = now;
        }
    }
}
// 實現2:單位時間內執行第一次(延遲執行),節流後面的
const throttle2 = (func, intervalTime = 100) => {
    // 定義falg,初試為true
    let flag = true;
    // 返回的函式是每次使用者實際呼叫的節流函式
    return (...args) => {
        const ctx = this;
        // 如果flag為true,則執行定時器
        if (flag) {
            setTimeout(() => {
                func.apply(ctx, args);
                // 函式執行完畢後=true;
                flag = true;
            }, intervalTime);
        }
        //沒執行完成前都為false
        flag = false;
    };
}
// 實現3:執行首次+最後一次,節流中間的,比較綜合的節流方式!
function throttleMiddle(func, intervalTime = 100) {
    let timer = null;
    let startTime = 0;
    return (...args) => {
        const ctx = this;
        const now = Date.now();
        if (startTime && now < startTime + intervalTime) {
            //替換前面的
            clearTimeout(timer);
            timer = setTimeout(() => {
                startTime = now;
                func.apply(ctx, args);
            }, Math.max(intervalTime - (Date.now() - startTime), 0)); //剩餘等候時間
        } else { //每輪首次會執行,立即執行
            startTime = now;
            func.apply(ctx, args);
        }
    }
}
// 節流-函式擴充套件,使用的throttleMiddle版本
Function.prototype.throttle = function (intervalTime = 100) {
    let func = this;
    let startTime, timer = null;
    //這裡不能用箭頭函式,會導致this汙染
    return function (...args) {
        const ctx = this;
        let now = Date.now();
        if (startTime && now < startTime + intervalTime) {
            clearTimeout(timer);
            timer = setTimeout(() => {
                func.apply(this, args);
                startTime = now;
            }, Math.max(intervalTime - (now - startTime), 0));
        }
        else {
            startTime = now;
            func.apply(ctx, args);
        }
    }
}

?適用場景

  • ✅ 定時秒殺、抽獎按鈕,執行多次提交,避免太過頻繁的提交把服務端搞崩了。
  • ✅ 連續的UI互動,如DOM拖拽,在視窗的resize事件、視窗的滾動scroll事件中更新UI,如果用防抖會有卡頓的現象,更適合用節流。

?使用案例:滾動瀏覽器捲軸到末尾。

  • 如果不用防抖函式,scroll事件頻繁觸發,共觸發了29次。
  • 加上節流函式,同樣的速度移動,執行了4次,間隔均勻。
//移動瀏覽器捲軸到末尾
window.addEventListener('scroll', print); //執行了29次
//加上節流,延遲300ms
window.addEventListener('scroll', throttle(print, 300)); //執行了4次
let index = 0;
function print() {
    console.log(index++);
}

©️版權申明:版權所有@安木夕,本文內容僅供學習,歡迎指正、交流,轉載請註明出處!原文編輯地址-語雀

相關文章