面试常考之防抖节流, 搞定面试官第一步!

44 阅读4分钟

“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情

前言

防抖(debounce)和节流(throttle), 一直以来都是面试中的高频考点. 它们并不难, 可在你毫无准备的时候, 面试官要求手写一个防抖或节流, 许多面试者就会有些措手不及了, 笔者也是其中之一.
在经历痛苦面具后, 痛定思痛, 赶紧去学习了一下他们的手写, 在这里分享给大家. 干货满满, 建议收藏.🤗

概念

防抖和节流的概念想必大家都很清楚, 这里我就简单概述一下

防抖: 规定n 秒后执行回调, 在n 秒内触发则重新计时.
节流: 不管触发多少次, n 秒后只执行一次回调

有一个很好的例子
假设有一个电梯, 每次有人进来就等待15秒再关门, 每次有人进来就重新计时五秒,这就是防抖.
如果它每隔15秒就关门, 期间不管进来多少人,都保持这个时间间隔不变, 这就是节流.

开始手写debounce !

废话不多说, 直接先来个防抖的手写
debounce 接收两个参数, 一个回调函数和一个时间, 并返回一个函数

function debounce(func, time) {
    // 在闭包空间中, 声明一个timer 作定时器, 
    // 它永久存在, 以便每次触发回调都可以操作到同一个timer
    let timer
    return function() {
    // 接收外界传来的参数列表
        let args = arguments
    // 每次触发把之前的定时清除, 并设置新的计时, 重新计时
        clearTimeout(timer)
        timer = setTimeout(() => {
    // 使用apply 改变func 函数this 指向
    // 否则当函数运行的时候, this 会指向全局
            func.apply(this, args)
        }, time)
    }
}

这里注意: 一定要改变this 指向, 否则setTimeout 在执行的之后, this 指向全局, setTimeout 中的函数也指向全局

debounce 场景应用

先来一个简单的输入框, 输入数据到页面


  <input type="text" id="input" />
  <div id="text"></div>

  <script>
    let text = document.getElementById('text')
    let input = document.getElementById('input')

    const inputText =(e) => {
      text.innerHTML = e.target.value
    }

    input.addEventListener('keyup', inputText)

  </script>

我们来看看效果

1663566737938 -original-original.gif

可以看到, 每次输入一个字母就推送一个字母到页面上
这个时候, 我们要需要用debounce 来做下优化

<input type="text" id="input" />
  <div id="text"></div>

  <script>
    let text = document.getElementById('text')
    let input = document.getElementById('input')

    const inputText =(e) => {
      text.innerHTML = e.target.value
    }
   
    let debounceInput = debounce(inputText, 500)

    input.addEventListener('keyup', debounceInput)

  </script>

我们再来看看防抖之后的效果

QQ录屏20220919151958 -original-original.gif

这样我们就得到了一个实现了防抖效果的函数, 是不是很简单, 接下来我们来看看节流函数

手写throttle !

废话不多少, 先把它写了再解释

    function throttle(func, time) {
       // 时间戳结合定时器
       // 单纯时间戳最后一次不会触发回调
       // 所以需要一个定时器, 给最后一次定时
       let t1, deferTimer
       return function() {
           let args = arguments
           let that = this
           let t2 = +new Date()
           // 每次触发将之前的定时清除
           clearTimeout(deferTimer)
           // t1 不为undefined 是其中一个条件
           // 只有当节流触发过之后 t1 = t2 , 这时t1 才有值
           
           if(t1 && t2 - t1 < time) {
               deferTimer = setTimeout(() => {
                   func.apply(that, args)
                   t1 = t2
               }, (time - (t2 - t1)))
           // 间隔时间超过time 的时候执行回调
           }else {
               func.apply(that, args)
               t1 = t2
           }
    }

到这里, 一个节流函数就写好了, 接下来我们看看它的使用场景


    <style>
        div {
          width: 300px;
          height: 300px;
          background-color: pink;
          font-size: 30px;
          color: white;
          text-align: center;
          line-height: 300px;
        }
    <style/>


    <div id="text">0</div>
    <script>
        let text = document.getElementById('text')
   
        let count = 0
        const moveText = () => {
          text.innerHTML = count++
        }

        let throttleInput = throttle(moveText, 2000)

        text.addEventListener('mousemove', throttleInput)

    <script/>

让我们来看看效果吧

QQ录屏20220925212201 -original-original.gif

可以看到,当我们一直一定鼠标的时候,它会在两秒的间隔后加1

值得一提的是,当我们停下鼠标,这时经过定时器数字来到了6

我们继续停止鼠标不动,等时间超过两秒,再次移动鼠标,神奇的事情发生了! 它立即跳动变成了数字7
这究竟是怎么回事呢,我们一起来分析分析
首先,当来到数字6时,定时器触发,t1 = t2 被执行, 当等待时间超过2秒时, 我们移动鼠标, 获取到新的t2 ,这时的t2 - t1 > 2000 ,所以else 中的语句被立即执行,即回调函数立即执行。

总结

用简单一点的话来说: 防抖是控制次数, 节流是控制频率。

写在最后

看到这里的小伙伴,如果这篇文章有帮助到你,请给我一个大大的赞👍~ 😊     

文章中有不足的地方,也可以在评论中指出,感谢~🤗🤗🤗