# Debounce 防彈跳 & Throttle 節流

# 簡介

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

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

## 使用場景

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

## Debounce 防彈跳

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

基本函數架構

```javascript
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;
}
```

使用範例

```javascript
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 節流

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

```javascript
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;
        }
    }
}
```

```javascript
function throttle(method, wait) {
    let timeout;

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

使用範例

```javascript
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

- [React Glossary about Debounce & Throttle](https://github.com/reactwg/react-18/discussions/46#discussioncomment-846733)

