真正搞懂防抖函数

时间:2021-1-18 作者:admin

前言:

这两天看面试题,又遇到了防抖和节流,之前也是似懂非懂,于是到处看如何实现防抖和节流,可是很可惜,大部分只有代码没有实际场景和注释,于是自己弄懂后记录下来。

防抖函数

  • 定义:顾名思义,在频繁触发的前提下,让函数只在特定的时间内没有触发执行条件才执行一次代码。
  • 使用场景:频繁操作点赞和取消点赞,因此需要获取最后一次操作结果并发送给服务器。

自己模拟了一个使用场景,即点击提交按钮会触发ajax请求,但是如果用户频繁点击提交按钮,难道就要触发n次吗,显然不可取。

  1. 那么如何做呢?先写下初始代码。通过 console.log(1) 去模拟ajax 请求的场景。现在只要点击按钮就会打印 1 ,n 次点击就打印 n 次 1 。
<input type="text">
<button id="submit">提交</button>
let btn=document.getElementById('submit')
btn.addEventListener('click',submit)

function submit(){
    console.log(1)
}
  1. 有了场景,就要开始思考了,如何让 submit 函数防抖呢?首先给它包装一个 debounce 函数。但是这里有一个问题,在页面刷新时,会发现浏览器会自动打印 1 和 debounce。这是为什么呢?
btn.addEventListener('click',debounce(submit,1000))
function debounce(fn,delay){
    fn()
    console.log("debounce")
}
  1. 这是因为 addEventListener 中接收的第二个参数是立即执行函数,所以会导致 debounce 函数被执行。

  2. 那么就得想办法如何让 debounce 不执行。其实改写为回调函数即可。

function debounce(fn,delay){
  return function(){
    fn()
  }
}
  1. 接着开始思考,如何在点击按钮时,让函数延迟执行呢?
function debounce(fn,delay){
  return function(){
    setTimeout(() => {
      fn()
    }, delay);
  }
}
  1. 可是光延时哪儿行,该触发 n 次还是触发 n 次。就得想办法在第二次点击时让计时器重新计时。首先在回调函数外面定义一个 timer 用来接收计时器,由于 debounce 在页面加载时就会自动执行,所以 timer 初始值就是 null , 并且在后续点击按钮时,是直接触发的回调函数,不会去重新定义 timer 。
function debounce(fn,delay){
  let timer=null
  return function(){
    if(timer){
      clearTimeout(timer)
    }
    timer=setTimeout(() => {
      fn()
    }, delay);
  }
}
  1. 大致上,已经完成了防抖的基本操作,也能理解了,但是会有几个小问题。

  2. 关于 submit 函数的 this 指向问题。可以发现这里的 this 实际是 window 对象,为什么呢?这是因为 submit 是在计时器中被调用的。可是计时器中的 this 为什么指向按钮呢,这是因为计时器是箭头函数,箭头函数是不具备 this 的,this 取决于外层的函数,这里外层函数为回调函数,而这个回调函数指向 btn 。那么 fn() 是直接调用,所以它的 this 指向 window 。

function submit(){
  console.log(this);
}
function debounce(fn,delay){
  let timer=null
  return function(){
    if(timer){
      clearTimeout(timer)
    }
    timer=setTimeout(()=>{
      console.log(this);
      fn()
    }, delay);
  }
}
  1. 解决 submit 的 this 指向问题。这里只需要使用 apply 函数去改变 this 指向即可。
timer=setTimeout(()=>{
  console.log(this);
  fn.apply(this)
}, delay);
  1. 关于事件对象的获取。如果改写为如下就可以获取到 e 。
function submit(){
  console.log(e);
}
function debounce(fn,delay){
  return function(e){
    console.log(e);
  }
}
  1. 按照防抖函数的写法。debounce 可以获取到 e ,而 submit 是获取不到的。所以要思考如何传递参数。
function submit(){
  console.log(e);
}
function debounce(fn,delay){
  let timer=null
  return function(e){
    if(timer){
      clearTimeout(timer)
    }
    timer=setTimeout(()=>{
      console.log(e);
      fn.apply(this)
    }, delay);
  }
}
  1. arguments 可以用来传递参数,在回调函数中,arguments 就是传递给函数的所有参数集合。
function submit(e){
  console.log(e);
}
function debounce(fn,delay){
  let timer=null
  return function(){
    if(timer){
      clearTimeout(timer)
    }
    timer=setTimeout(()=>{
      fn.apply(this,arguments)
    }, delay);
  }
}

具体 html 代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>防抖</title>
</head>
<body>
  <input type="text">
  <button id="submit">提交</button>

  <script>
    let btn=document.getElementById('submit')
    btn.addEventListener('click',debounce(submit,1000))

    function submit(e){
      console.log(11111);
    }
    function debounce(fn,delay){
      let timer=null
      return function(){
        if(timer){
          clearTimeout(timer)
        }
        timer=setTimeout(()=>{
          fn.apply(this,arguments)
        }, delay);
      }
    }
  </script>
</body>
</html>
声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。