函数防抖与节流

72 阅读2分钟

工作中,有时需要绑定一些持续触发的事件,如 resizescrollinputmousemove 等,但我们并不希望在事件持续触发的过程中频繁地去执行函数。

因为这不利于性能优化,如果是调用接口,还会对服务器造成极大的压力。所以我们需要使用“防抖”和“节流”来控制函数的调用次数,提高性能。

一、函数防抖

定义:事件响应函数在一段时间后才执行,如果在这段时间内再次调用,则重新计算执行时间;当预定的时间内没有再次调用该函数,则执行事件响应函数。

1. 最简单版本

function debounce(fn, wait) {
  let timer = null
  return function () {
    if (timer) clearTimeout(timer)
    timer = setTimeout(() => {
      fn()
      timer = null
    }, wait)
  }
}

demo:js.jirengu.com/hohujoyese/…

2. 指定 this

function debounce(fn, wait) {
  let timer = null
  return function () {
    if (timer) clearTimeout(timer)
    timer = setTimeout(() => {
      fn.call(this)
      timer = null
    }, wait)
  }
}

demo: js.jirengu.com/popoborabi/…

3. 传参

function debounce(fn, wait) {
  let timer = null
  return function (...args) {
    if (timer) clearTimeout(timer)
    timer = setTimeout(() => {
      fn.call(this, ...args)
      timer = null
    }, wait)
  }
}

demo: js.jirengu.com/yaqivoxuca/…

4. 立即执行(第一次执行)

function debounce(fn, wait, immediate) {
  let timer = null
  return function (...args) {
    if (timer) clearTimeout(timer)
    if (immediate) {
      let callNow = !timer
      if (callNow) fn.call(this, ...args)
      timer = setTimeout(() => {
        timer = null
      }, wait)
    } else {
      timer = setTimeout(() => {
        fn.call(this, ...args)
        timer = null
      }, wait)  
    }
  }
}

demo: js.jirengu.com/wiwuwacoli/…

5. 返回值

function debounce(fn, wait, immediate) {
  let timer = null
  let result = null
  return function (...args) {
    if (timer) clearTimeout(timer)
    if (immediate) {
      let callNow = !timer
      if (callNow) result = fn.call(this, ...args)
      timer = setTimeout(() => {
        timer = null
      }, wait)
    } else {
      timer = setTimeout(() => {
        result = fn.call(this, ...args)
        timer = null
      }, wait)  
    }
    return result
  }
}

demo: js.jirengu.com/laqeyinipe/…

6. 取消事件

function debounce(fn, wait, immediate) {
  let timer = null
  let result = null
  let _debounce = function (...args) {
    if (timer) clearTimeout(timer)
    if (immediate) {
      let callNow = !timer
      if (callNow) result = fn.call(this, ...args)
      timer = setTimeout(() => {
        timer = null
      }, wait)
    } else {
      timer = setTimeout(() => {
        result = fn.call(this, ...args)
        timer = null
      }, wait)  
    }
    return result
  }
  _debounce.cancel = function () {
    clearTimeout(timer)
    timer = null
  }
  return _debounce
}

demo: js.jirengu.com/fusuruduhu/…

二、函数节流

定义:如果持续触发事件,每隔一段时间,只执行一次事件。

1. 时间戳版(第一次立即执行,最后一次不执行)

function throttle(fn, wait) {
  let old = 0
  return function (...args) {
    let now = new Date().getTime()
    if (now - old > wait) {
      fn.call(this, ...args)
      old = now
    }
  }
}

demo: js.jirengu.com/siqesinari/…

2. 定时器版(第一次不立即执行,最后一次会执行)

function throttle(fn, wait) {
  let timer = null
  return function (...args) {
    if (!timer) {
      timer = setTimeout(() => {
        fn.call(this, ...args)
        timer = null
      }, wait)
    }
  }
}

demo: js.jirengu.com/fakawuxigu/…

3. 时间戳+计时器版(第一次立即执行,最后一次也会执行)

function throttle(fn, wait) {
  let old = 0
  let timer = null
  return function (...args) {
    let now = new Date().getTime()
    if (now - old > wait) {
      if (timer) {
        clearTimeout(timer)
        timer = null
      }
      fn.call(this, ...args)
      old = now
    }
    if (!timer) {
      timer = setTimeout(() => {
        fn.call(this, ...args)
        old = new Date().getTime()
        timer = null
      }, wait)
    }
  }
}

demo: js.jirengu.com/bosepojuva/…