Debounce 防彈跳 & Throttle 節流

簡介

Debounce 和 Throttle 常用在 inputscrollresize 等事件進行控制,防止事件過多觸發,減少資源消耗或是商業邏輯上的維護成本。這兩者最大的差異在於觸發的時機。

  • debounce(func, wait, options):建立並回傳函數的防彈跳版本,將延遲函數的執行(真正的執行)在函數最後一次調用時刻的 wait 毫秒之後,對於必須在一些輸入(多是一些用戶操作)停止之後再執行的行為有幫助。將一個連續的調用歸類為一個,如果連續在 wait 毫秒內調用,只有最後一次會執行
  • throttle(func, wait, options):建立並返回一個像節流閥一樣的函數,當重復調用函數的時候,最多每隔指定的 wait 毫秒調用一次該函數;不允許方法在每 wait 毫秒間執行超過一次,如果連續在 wait 毫秒內調用,最後執行會均勻分布在大約每 wait 一次

使用場景

  • 滑鼠移動時減少計算次數:debounce
  • 在輸入框中輸入文字時自動發送 Ajax 請求進行自動補全: debounce
  • API 請求合併,不希望短時間內大量的請求被重復發送:debounce
  • 縮放畫面重新計算樣式或佈局:debouncethrottle
  • 滾動時觸發操作:throttle
  • 對使用者輸入的驗證,不想停止輸入再進行驗證,而是每 n 秒進行驗證:throttle

Debounce 防彈跳

在事件被觸發 n 秒後,再去執行 callback。如果 n 秒內被重新觸發,則重新計時。

基本函數架構

function debounce(delay, callback) {
    let timeoutID;

    function wrapper() {
        const self = this;
        const args = arguments;

        function exec() {
            callback.apply(self, args);
        }
        // 重新計時
        clearTimeout(timeoutID);
        timeoutID = setTimeout(exec, delay);
    }

    return wrapper;
}

使用範例

let reduceEvent
function debounce(cb, delay) {
    if (!reduceEvent) {
        reduceEvent = setTimeout(() => {
            cb();
            console.log('執行啦!!');
            reduceEvent = null;
        }, delay);
    }
}

setTimeout(() => debounce(() => console.log(1), 2000), 1000) // Output: 1 執行啦!!
setTimeout(() => debounce(() => console.log(2), 2000), 2000)
setTimeout(() => debounce(() => console.log(3), 2000), 2000)
setTimeout(() => debounce(() => console.log(4), 2000), 4000) // Output: 4 執行啦!!

Throttle 節流

在規定時間內,將觸發的事件合併成一次執行。降低高頻率事件觸發的頻次

function throttle(method, wait) {
    // 對比時間,初始化為 0 則首次觸發立即執行,初始化為當前時間,則 wait 毫秒後觸發才會執行
    let previous = 0;
    return function(...args) {
        let context = this;
        let now = new Date().getTime();

        // 間隔大於 wait 則執行 method 並更新對比時間戳
        if (now - previous > wait) {
            method.apply(context, args);
            previous = now;
        }
    }
}
function throttle(method, wait) {
    let timeout;

    return function(...args) {
        let context = this;
        if (!timeout) {
            timeout = setTimeout(() => {
                timeout = null;
                method.apply(context, args);
            }, wait);
        }
  }
}

使用範例

function throttle(func, wait = 200) {
    let last = 1
    let timer
    return function(...rest) {
        const now = +new Date()
        if (last && now - last < wait) {
            clearTimeout(timer)
            timer = setTimeout(() => {
                last = now
                func.apply(this, rest)
            }, wait)
        } else {
            last = now
            func.apply(this, rest)
            clearTimeout(timer)
        }
    }
}
const task = throttle(() => console.log(1), 2000)
setTimeout(task, 0)
setTimeout(task, 500)
setTimeout(task, 1000)
setTimeout(task, 2000) // Output: 1 1

Reference

Did you find this article valuable?

Support 攻城獅 by becoming a sponsor. Any amount is appreciated!