# 手写防抖与节流函数

## 前言

函数防抖与节流算是前端性能优化的一个细节点，可能开发大多数是用着 JS 库顺带的防抖节流函数，但我们必须会自己写因为没有必要因为一个函数引入一个库。

## 防抖与节流效果

这是别人做的一个动画，很容易就能理解区别：[防抖与节流](http://demo.nimius.net/debounce_throttle/)

## 函数防抖(debounce)

> 在事件被触发 n 秒后再执行回调，如果在这 n 秒内又被触发，则重新计时。

```javascript
function debounce(func, wait, immediate) {
  let timeout
  function debounced(...args) {
    const ctx = this
    if (timeout) clearTimeout(timeout) // 每次调用debounce函数都会将前一次的timer清空，确保只执行一次

    if (immediate) {
      const callNow = !timeout
      timeout = setTimeout(() => {
        // 这里只有在 wait 时长之后，timeout 为 null，然后触发防抖函数才能立即执行，
        // 否则 callNow 为 false，不会立即执行，计时器会重新计时
        timeout = null
      }, wait)
      if (callNow) func.apply(ctx, args)
    } else {
      timeout = setTimeout(() => {
        func.apply(ctx, args)
      }, wait)
    }
  }
  debounced.cancel = function cancel() {
    // 取消功能
    clearTimeout(timeout)
    timeout = null
  }
  return debounced
}
```

### 应用场景

适合多次事件一次响应的情况

- search 搜索联想，用户在不断输入值进行 AJAX 验证时时，用防抖来节约请求资源。
- 按钮点击事件/input 事件，防止用户多次重复提交
- 电话号码输入的验证， 只需停止输入后进行一次。

## 函数节流(throttle)

> 规定一个单位时间，在这个单位时间内，只能有一次触发事件的回调函数执行，如果在同一个单位时间内某事件被触发多次，只有一次能生效。

### 定时器版

```javascript
function throttle(func, wait) {
  let timeout = 0
  return function throttled(...args) {
    const ctx = this
    if (!timeout) {
      timeout = setTimeout(() => {
        func.apply(ctx, args)
        timeout = null
      }, wait)
    }
  }
}
```

### 时间戳节流

```javascript
function throttle(func, wait) {
  let previous = 0
  return function throttled(...args) {
    const ctx = this
    const now = Date.now()
    const remain = wait - (now - previous)
    if (remain <= 0) {
      func.apply(ctx, args)
      previous = now
    }
  }
}
```

### 应用场景

适合大量事件按时间做平均分配触发。

- 监听滚动或 resize 事件，比如是否滑到底部自动加载更多，调整窗口大小用 throttle 来判断
- DOM 元素拖拽
- 自动保存草稿功能，当用户在输入的时候(一直触发事件),单位时间内只保存一次草稿
- Canvas 画笔功能
- 游戏中的刷新率
