函数防抖与节流

803 阅读3分钟

函数防抖与节流

日常工作中,经常遇到这样一个问题,在滚动事件中需要做个复杂计算或者实现一个按钮的防二次点击操作。

  • 防抖和节流的作用都是防止函数多次调用。区别在于,假设一个用户一直触发这个函数,且每次触发函数的间隔小于wait,防抖的情况下只会调用一次,而节流的 情况会每隔一定时间(参数wait)调用函数。

函数防抖(debounce)

  • 例如在搜索引擎搜索问题的时候,我们是希望用户输入完最后一个字才调用查询接口,这个时候适用延迟执行的防抖函数,它总是在一连串(间隔小于wait的)函数触发之后调用。
const debounce = (fn, wait = 800) => {
    let timer = null
    return function() {
      if (timer) clearTimeout(timer)
      timer = setTimeout(() => {
        // 用 .apply 保证 sayHi 里面的 this 指向事件的调用者,否则指向 window
        fn.apply(this, arguments)
      }, wait);
    }
  }
 
 function sayHi() {
     console.log('防抖成功')
 }
 
let inp = document.querySelector('#input')

inp.addEventListener('input', debounce(sayHi))

  • 上面这个函数有一点缺陷:会存在一种立即执行函数,比如(我们在点击 star 的时候,希望是点击第一次的时候就生效,而不是最后一次生效)。改进如下:
const debounce = (fn, wait = 800, immediate = true) => {
    let timer, context, args

    // 延迟执行函数
    const later = () => setTimeout(() => {
    // 延迟函数执行完毕,清空缓存的定时器序号
      timer = null
      if (!immediate) {
        fn.apply(context, args)
        context = args = null
      }
    }, wait);

    // 返回的函数是每次实际调用的函数
    return function(...params) {
      // 如果没有创建延迟函数 later, 就创建一个
      if (!timer) {
        timer = later()
        // 若是立即执行函数,则调用函数
        // 否则缓存参数和调用上下文
        if (immediate) {
          fn.apply(this, params)
        } else {
          context = this
          args = params
        }

         // 如果已经有延迟执行函数 (later), 调用的时候清除原来的并重新设定一个
      } else {
        clearTimeout(timer)
        timer = later()
      }
    }
  }

  function sayHi() {
    console.log(this);
    console.log('防抖成功');
  }

  let inp = document.querySelector('#input')

  inp.addEventListener('input', debounce(sayHi))

函数节流(throttle)

  • 防抖动是将多次执行变为最后一次执行,节流是将多次执行变成每隔一段时间执行。
const throttle = (fn, wait = 400) => {
  let canRun = true
  return function (...args) {
    if (!canRun) return
    canRun = false

    setTimeout(() => {
      fn.apply(this, args)
      // 最后在 setTimeout 执行完毕后再把标记设置为 true (关键)表示可以执行下一次循环了。当定时器没有执行的时候标记永远是 false,在开头被 return 掉
      canRun = true
    }, wait);
  }
}

function sayHi() {
    console.log(this);
    console.log('节流成功');
  }

let inp = document.querySelector('#input')

inp.addEventListener('input', throttle(sayHi))

总结(使用场景):

函数防抖:

  • search搜索联想,用户在不断输入值时,用防抖来节约请求资源。
  • window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次

函数节流:

  • 鼠标不断点击触发,mousedown(单位时间内只触发一次);
  • 监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断

PS: 生产环境中,建议用 lodash 中的 debouncethrottle。毕竟那么多人在用,出现问题的概率会很小。