Skip to content
导航栏

防抖和节流

1. 防抖

1.1 一图胜千言

  • 当用户停止操作后的一段时间内才会执行一次,如果用户一直操作,就不会执行
  • 适用于输入框的输入事件,滚动事件,窗口大小改变事件等

1.2 简易版防抖函数实现

js
const debounce = (fn, delay) => {
    // 闭包
    let timer = null;

    // 真正执行的函数
    const _debounce = function () {
        // 取消上一次定时器
        if(timer) clearTimeout(timer)
        // 延迟执行
        timer = setTimeout(() => {
            fn()
        }, delay)
    }

    return _debounce()
}

1.3 防抖增加this执行和参数传递

js
function debounce(fn, delay) {
    let timer = null;
    return function (...args) {
        if(timer) clearTimeout(timer);

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

1.4 防抖增加立即执行

js
function debounce(fn, delay, immediate = false) {
    let timer = null;
    let isInvoke = false;// 这个东西是否之前执行过

    return function (...args) {
        if(timer) clearTimeout(timer);

        // 判断是否需要立即执行
        if(immediate && !isInvoke) {
            fn.apply(this, args)
            isInvoke = true
        }else {
            timer = setTimeout(() => {
                fn.apply(this, args)
                isInvoke = false
            },delay)
        }

    }
}

1.5 防抖增加取消功能

js
function debounce(fn, delay, immediate = false) {
    let timer = null;
    let isInvoke = false;// 这个东西是否之前执行过

    function _debounce(...args) {
        if(timer) clearTimeout(timer);

        // 判断是否需要立即执行
        if(immediate && !isInvoke) {
            fn.apply(this, args)
            isInvoke = true
        }else {
            timer = setTimeout(() => {
                fn.apply(this, args)
                isInvoke = false
            },delay)
        }
    }

    _debounce.cancel = function () {
        if(timer) clearTimeout(timer)
        timer = null
        isInvoke = false
    }


    return _debounce
}

1.6 防抖增加返回值

js
function debounce(fn, delay, immediate = false, callBack) {
    let timer = null;
    let isInvoke = false;// 这个东西是否之前执行过

    function _debounce(...args) {
        return new Promise((resolve,reject) => {
            if(timer) clearTimeout(timer);

            // 判断是否需要立即执行
            if(immediate && !isInvoke) {
                const result = fn.apply(this, args)
                if(callBack) callBack(result)
                resolve(result)
                isInvoke = true
            }else {
                timer = setTimeout(() => {
                    const result = fn.apply(this, args)
                    if(callBack) callBack(result)
                    resolve(result)
                    isInvoke = false
                },delay)
            }
        })
    }

    _debounce.cancel = function () {
        if(timer) clearTimeout(timer)
        timer = null
        isInvoke = false
    }


    return _debounce
}

2. 节流

2.1 一图胜千言

  • 当用户一直操作时,每隔一段时间执行一次
  • 监听滚动事件,用户频繁点击按钮, 飞机大战游戏等

2.2 简易版防抖函数实现

js
function throttle (fn, interval) {
    let lastTime = 0;

    const _throttle = function () {
        const nowTime = new Date().getTime()
        const remainTime = interval - (nowTime - lastTime)
        if(remainTime <= 0){
            fn()
            lastTime = nowTime
        }
    }


    return _throttle
}

2.3 leading: 实现防抖函数第一次不执行(可选)

js
function throttle (fn, interval, options = { leading: true, trailing: false}) {
    // 1. 记录上一次开始的时间
    const { leading , trailing } = options
    let lastTime = 0;

    const _throttle = function () {
        const nowTime = new Date().getTime()
        // 是第一次执行并且leading不需要第一次执行
        if(!lastTime && !leading) lastTime = nowTime


        const remainTime = interval - (nowTime - lastTime)
        if(remainTime <= 0){
            fn()
            lastTime = nowTime
        }
    }


    return _throttle
}

2.4 trading: 输入突然停止,最后一次也触发

js
function throttle(fn, interval, options = { leading: true, trailing: false }) {
    // 1.记录上一次的开始时间
    const { leading, trailing } = options
    let lastTime = 0
    let timer = null

    // 2.事件触发时, 真正执行的函数
    const _throttle = function() {

        // 2.1.获取当前事件触发时的时间
        const nowTime = new Date().getTime()
        if (!lastTime && !leading) lastTime = nowTime

        // 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 计算出还剩余多长事件需要去触发函数
        const remainTime = interval - (nowTime - lastTime)
        if (remainTime <= 0) {
            if (timer) {
                clearTimeout(timer)
                timer = null
            }

            // 2.3.真正触发函数
            fn()
            // 2.4.保留上次触发的时间
            lastTime = nowTime
            return
        }

        if (trailing && !timer) {
            timer = setTimeout(() => {
                timer = null
                lastTime = !leading ? 0: new Date().getTime()
                fn()
            }, remainTime)
        }
    }

    return _throttle
}

2.5 this参数

js
function throttle(fn, interval, options = { leading: true, trailing: false }) {
  // 1.记录上一次的开始时间
  const { leading, trailing } = options
  let lastTime = 0
  let timer = null

  // 2.事件触发时, 真正执行的函数
  const _throttle = function(...args) {

    // 2.1.获取当前事件触发时的时间
    const nowTime = new Date().getTime()
    if (!lastTime && !leading) lastTime = nowTime

    // 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 计算出还剩余多长事件需要去触发函数
    const remainTime = interval - (nowTime - lastTime)
    if (remainTime <= 0) {
      if (timer) {
        clearTimeout(timer)
        timer = null
      }

      // 2.3.真正触发函数
      fn.apply(this, args)
      // 2.4.保留上次触发的时间
      lastTime = nowTime
      return
    }

    if (trailing && !timer) {
      timer = setTimeout(() => {
        timer = null
        lastTime = !leading ? 0: new Date().getTime()
        fn.apply(this, args)
      }, remainTime)
    }
  }

  return _throttle
}

2.6 取消功能

js
function throttle(fn, interval, options = { leading: true, trailing: false }) {
  // 1.记录上一次的开始时间
  const { leading, trailing } = options
  let lastTime = 0
  let timer = null

  // 2.事件触发时, 真正执行的函数
  const _throttle = function(...args) {

    // 2.1.获取当前事件触发时的时间
    const nowTime = new Date().getTime()
    if (!lastTime && !leading) lastTime = nowTime

    // 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 计算出还剩余多长事件需要去触发函数
    const remainTime = interval - (nowTime - lastTime)
    if (remainTime <= 0) {
      if (timer) {
        clearTimeout(timer)
        timer = null
      }

      // 2.3.真正触发函数
      fn.apply(this, args)
      // 2.4.保留上次触发的时间
      lastTime = nowTime
      return
    }

    if (trailing && !timer) {
      timer = setTimeout(() => {
        timer = null
        lastTime = !leading ? 0: new Date().getTime()
        fn.apply(this, args)
      }, remainTime)
    }
  }

  _throttle.cancel = function() {
    if(timer) clearTimeout(timer)
    timer = null
    lastTime = 0
  }

  return _throttle
}

2.7 函数返回值

js
function throttle(fn, interval, options = { leading: true, trailing: false }) {
  // 1.记录上一次的开始时间
  const { leading, trailing, resultCallback } = options
  let lastTime = 0
  let timer = null

  // 2.事件触发时, 真正执行的函数
  const _throttle = function(...args) {
    return new Promise((resolve, reject) => {
      // 2.1.获取当前事件触发时的时间
      const nowTime = new Date().getTime()
      if (!lastTime && !leading) lastTime = nowTime

      // 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 计算出还剩余多长事件需要去触发函数
      const remainTime = interval - (nowTime - lastTime)
      if (remainTime <= 0) {
        if (timer) {
          clearTimeout(timer)
          timer = null
        }

        // 2.3.真正触发函数
        const result = fn.apply(this, args)
        if (resultCallback) resultCallback(result)
        resolve(result)
        // 2.4.保留上次触发的时间
        lastTime = nowTime
        return
      }

      if (trailing && !timer) {
        timer = setTimeout(() => {
          timer = null
          lastTime = !leading ? 0: new Date().getTime()
          const result = fn.apply(this, args)
          if (resultCallback) resultCallback(result)
          resolve(result)
        }, remainTime)
      }
    })
  }

  _throttle.cancel = function() {
    if(timer) clearTimeout(timer)
    timer = null
    lastTime = 0
  }

  return _throttle
}