JavaScript手写防抖、节流函数实现,包含立即执行控制,取消执行

294 阅读3分钟

防抖

首先来看一下防抖的概念:

在频繁的触发事件执行函数时,让要执行的函数不断的往后延迟执行,直到没有新的事件触发时才会执行函数执行

应用场景:

  • 输入框
  • 页面滚动
  • 页面缩放
  • 窗口拖拽

1.基础实现

// 在封装一个函数时应该考虑的:
  // -函数的参数
  // -函数的返回值
  // -内部实现

// 1.该函数接收一个函数和一个延迟执行时间
function mydebounce(fn, delay) {
  // 定义定时器变量,清除上一次的函数使用
  let timer = null;

  // _debounce才是真正事件触发时的执行函数,用args接收所有传递的参数,如:event等
  const _debounce = function(...args) {
    // 闭包的运用场景,如果有再次触发事件,那就清除上一次事件触发的回调函数
    if(timer) clearTimeout(timer)

    // 定时器,每次有输入时就延迟delay时间后执行
    timer = setTimeout(() => {
      // 为要执行的fn绑定this和传递args参数,_debounce一定要用function定义,箭头函数没有this
      fn.apply(this, args);

      // 全部执行完毕后将timer还原为初始状态
      timer = null;
    }, delay);
  };
  
  return _debounce;
}

1. 取消函数执行功能

在函数执行过程中,如果想要取消这个函数执行,该怎么做呢?

答案:手动清除定时器

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

  const _debounce = function(...args) {
    if(timer) clearTimeout(timer)

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

  // 取消功能添加
  _debounce.cancel = function() {
    if(timer) clearTimeout(timer)
  }

  return _debounce

}

2. 立即执行功能

比如在购物网站搜索时,第一次输入直接执行一次函数去请求相关资源,该怎么才能再输入时立即执行呢?

答案:通过传入第三个参数immerdiate来判断,只有immediate为true时并且函数没有执行过才立即执行一次

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

  const _debounce = function (...args) {
    if (timer) clearTimeout(timer)
    
    // 立即执行
    if (immediate && !isInvoke) {
      fn.apply(this, args)
      isInvoke = true
      return
    }

    // 延时执行
    timer = setTimeout(() => {
      fn.apply(this, args)
      timer = null
      isInvoke = false
    }, delay)

  }

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

  return _debounce
}

全部代码示例

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <input type="text">
  <button>取消函数执行</button>
  <script>

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

      const _debounce = function (...args) {
        if (timer) clearTimeout(timer)

        // 立即执行
        if (immediate && !isInvoke) {
          fn.apply(this, args)
          isInvoke = true
          return
        }

        // 延时执行
        timer = setTimeout(() => {
          fn.apply(this, args)
          timer = null  // 执行完成后把数据复原到初始的状态
          isInvoke = false
        }, delay)

      }

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

      return _debounce
    }

    const inputEl = document.querySelector("input")
    const btnCancelEl = document.querySelector("button")

    const debounceFn = debounce(function (event) {
      console.log(this.value, event)
    }, 2000, true)

    inputEl.oninput = debounceFn
    btnCancelEl.onclick = function () {
      debounceFn.cancel()
    }

  </script>

</body>

</html>

节流

再看一下节流的概念

在频繁的触发事件执行函数时,让要执行的函数按固定的频率去执行。比如:每隔一段时间就执行一次

运用场景:

  • 输入框
  • 鼠标移动事件
  • 用户频繁点击按钮操作
  • 游戏中(如:飞机大战发射子弹)

1. 基础实现

const throttle = function(fn, interval) {
  
  // 闭包保留函数输入的开始时间
  let startTime = 0
  
  const _throttle = function(...args) {
    let nowTime = new Date().getTime()
    // 核心逻辑,等待时间 = 频率时间-(当前时间 - 函数输入的开始时间)
    const waitTime = interval - (nowTime - startTime)
    // 当等待时间小于等于0时,说明到达了可以执行的频率时间
    if(waitTime <= 0) {
      fn.apply(this, args)
      // 每一次执行完成后要将开始时间赋值为当前时间
      startTime = nowTime
    }
  }
  
  return _throttle
}

2. 立即执行控制

const throttle = function(fn, interval, immediate = true) {
  let startTime = 0
  const _throttle = function(...args) {
    let nowTime = new Date().getTime()

    // 立即执行控制,第一次输入时要不要立即执行,默认是执行的,当immediate为false时就不要立即执行
    if(!immediate && startTime === 0) {
      startTime = nowTime
    }

    const waitTime = interval - (nowTime - startTime)
    if(waitTime <= 0) {
      fn.apply(this, args)
      startTime = nowTime
    }
  }
  return _throttle

}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <input type="text">
  <script>

    // 节流函数
    const throttle = function(fn, interval, immediate = true) {
      let startTime = 0
      const _throttle = function(...args) {
        let nowTime = new Date().getTime()

        if(!immediate && startTime === 0) {
          startTime = nowTime
        }

        const waitTime = interval - (nowTime - startTime)
        if(waitTime <= 0) {
          fn.apply(this, args)
          startTime = nowTime
        }
      }
      return _throttle

    }
    const inputEl = document.querySelector("input")
    inputEl.oninput = throttle(function(event) {
      console.log("输入事件响应", this.value, event)
    }, 1000, false)
  </script>
  
</body>
</html>