防抖是控制次数,节流是控制频率

防抖(debounce)

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

效果:如果短时间内大量触发同一事件,只会执行一次函数

适用场景:

  • 防止多次提交按钮,只执行最后提交的一次
  • 防止搜索框联想频繁发送请求
  • 防止频繁监听浏览器滚动事件
  • ……

示例:

假设鼠标滑过一个 div,触发 onmousemove 事件,它内部的文字会显示当前鼠标的坐标

demo 示例

防抖节流的原理、区别及应用 - 图1

在上边的场景下,我们不希望触发一次就执行一次,这就要用到防抖或节流

封装一个函数,让持续触发的事件监听是我们封装的这个函数,将目标函数作为回调(func)传进去,等待一段时间过后执行目标函数

  1. function debounce(func, wait) {
  2. let timeout // 由于返回的函数是一个闭包,所以 timeout 并不会回收
  3. return function() {
  4. let context = this;
  5. let args = arguments;
  6. if (timeout) clearTimeout(timeout) // 如果持续触发,那么就清除定时器,定时器的回调就不会执行
  7. timeout = setTimeout(() => {
  8. func.apply(context, args)
  9. }, wait)
  10. }
  11. }

demo 示例

防抖节流的原理、区别及应用 - 图2

上面的示例是非立刻执行的,也就是最后才执行一次,而立即执行的是一旦触发事件后会立即执行一次

  1. function debounce(func, wait) {
  2. let timeout
  3. return function() {
  4. let context = this;
  5. let args = arguments;
  6. if (timeout) clearTimeout(timeout) // 清除之后还是为 true
  7. let callNow = !timeout;
  8. timeout = setTimeout(() => {
  9. timeout = null;
  10. }, wait)
  11. if (callNow) func.apply(context, args)
  12. }
  13. }

可以将非立即执行版和立即执行版的防抖函数结合起来

/**
 * @desc 函数防抖
 * @param func 函数
 * @param wait 延迟执行毫秒数
 * @param immediate true 表立即执行,false 表非立即执行
 */
function debounce(func,wait,immediate) {
  let timeout;

  return function () {
    let context = this;
    let args = arguments;

    if (timeout) clearTimeout(timeout);
    if (immediate) {
      var callNow = !timeout;
      timeout = setTimeout(() => {
        timeout = null;
      }, wait)
      if (callNow) func.apply(context, args)
    }
    else {
      timeout = setTimeout(function(){
        func.apply(context, args)
      }, wait);
    }
  }
}

节流(throttle)

原理:设置一个开关,持续触发时关闭开关,n 秒后执行函数并打开开关

效果:如果单位时间内触发多次函数,只有一次生效

适用场景:

  • 拖拽场景防止超高频次触发位置变动
  • 缩放场景防止频繁监控浏览器 resize
  • 防止搜索框联想频繁发送请求,在指定单位时间内又必须触发
  • ……

示例:

同样是触发 onmousemove 事件

function throttle (func, delay) {
  let timeout
  return function () {
    let context = this
    let args = arguments
    if (!timeout) {
      timeout = setTimeout(() => {
        timeout = null
        func.apply(context, args)
      }, delay)
    }
  }
}

demo 示例

防抖节流的原理、区别及应用 - 图3

上面的示例是利用定时器,也可以利用时间戳

function throttle (func, wait) {
  let previous = 0
  return function () {
    let now = Date.now()
    let context = this
    let args = arguments
    if (now - previous > wait) {
      func.apply(context, args)
      previous = now
    }
  }
}

两者相结合:

/**
 * @desc 函数节流
 * @param func 函数
 * @param wait 延迟执行毫秒数
 * @param type 1 表时间戳版,2 表定时器版
 */
function throttle (func, wait, type) {
  if (type === 1) {
    let previous = 0
  } else if (type === 2) {
    let timeout
  }
  return function () {
    let context = this
    let args = arguments
    if (type === 1) {
      let now = Date.now()

      if (now - previous > wait) {
        func.apply(context, args)
        previous = now
      }
    } else if (type === 2) {
      if (!timeout) {
        timeout = setTimeout(() => {
          timeout = null
          func.apply(context, args)
        }, wait)
      }
    }
  }
}

参考文章