每日一题深度解答:什么是防抖和节流?有什么区别?如何实现?

523 阅读2分钟

防抖(debounce)

定义

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

应用场景

  • 搜索框输入发送AJAX请求
  • 窗口缩放,每次resize/scroll触发事件

用例

比如有一个关键词搜索的input框,用户输入后触发keyup事件向后端发送一个请求:

<div>
  <input id="input" type="text">
</div>

<script>
  const input = document.getElementById('input')
  input.addEventListener('keyup', (e) => {
    console.log(e.target.value)
  })
</script>

测试后可以发现,每次输入都会有打印结果,如果是请求后端接口,那么不仅会造成极大的浪费,而且实际应用中,用户也是输出完整的字符后,才会请求。下面我们优化一下:

  function debounce(fn, delay) {
    let timer = null

    return function(...args) {
      const context = this
      if(timer) {
        clearTimeout(timer)
      }
      const _agrs = args.join(',')
      timer = setTimeout(fn.bind(context, _agrs), delay)
    }
  }

  const consoleDebounce = debounce(console.log, 500)

  const input = document.getElementById('input')

  input.addEventListener('keyup', (e) => {
    consoleDebounce(e.target.value)
  })

在运行一次后可以看到,当在频繁的输入时,并不会打印结果,只有当你在指定间隔内没有输入时,才会执行函数。如果停止输入但是在指定间隔内又输入,会重新触发计时。

完整实现一个通用防抖函数

function debounce(fn, delay = 500, immediate = false) {
  let timer = null

  return function(...args) {
    const context = this
    // 是否立即执行一次
    if (immediate && !timer) {
      fn.apply(context, args)
    }

    if (timer) {
      clearTimeout(timer)
    }

    timer = setTimeout(() => {
      fn.apply(context, args)
    }, delay)
  }
}

节流(throttle)

定义:

指连续触发事件但是在 n 秒中只执行一次函数。 节流会稀释函数的执行频率

应用场景

  • DOM 元素的拖拽功能实现(mousemove)
  • 计算鼠标移动的距离(mousemove)
  • 监听滚动事件判断是否到页面底部自动加载更多内容
  • 按钮点击事件(多次点击 n 秒内只生效一次)

用例

一个 button 按钮,每次点击需要提交表单。

<div>
  <button id="button">提交</button>
</div>

<script>

  const button = document.getElementById('button')
  button.addEventListener('click', (e) => {
    console.log('click')
  })
 
</script>

上面代码的问题是,当我们快速点击 button 时,每次点击都会打印 'click',下面用节流函数优化一下。

const conThrottle = throttle(console.log, 2000)

  const button = document.getElementById('button')
  button.addEventListener('click', (e) => {
    conThrottle(1)
  })


  function throttle(fn, time) {
    let timer = null
    let flag = true

    return function(...args) {
      let context = this
      if (flag) {
        fn.apply(context, args)
        flag = false
        timer = null
      }

      if (!timer) {
        timer = setTimeout(() => {
          flag = true
        }, time)
      }
    }
  }

再次测试,可以发现不管我们点击的速度多么快,在每 2 秒内,函数只会执行 1 次。

完整实现时间戳版本 throttle 函数

  function throttle(fn, time) {
    let startTime = 0

    return function(...args) {
      let context = this
      let endTime = Date.now()

      if (endTime - startTime >= time) {
        fn.apply(context, args)
        startTime = endTime
      }
    }
  }

查看原文 关注github每日一道面试题详解