面试官:手写一个防抖

前言

防抖和节流是在前端开发中广泛应用的两种性能优化技术,它们的出现旨在解决一些常见的性能和用户体验问题。在面试中,面试官往往会提及这两个概念,以考察面试者对于性能优化的理解和实际应用的能力。

以下是对这两种技术的深入剖析。

应用场景

下面我用按钮事件来模拟一下:

html 复制代码
<body>
    <button id="btn">提交</button>
    <script>
        let btn = document.getElementById('btn');
        btn.addEventListener('click',function(){console.log('提交了')})
    </script>
</body>

我们在页面上创建一个了按钮。当用户点击按钮时,会触发一个事件,例如提交表单或执行某个操作,我们先用打印模拟这个操作。在某些情况下,用户可能会快速点击按钮,比如误触,多点了几下,导致事件被触发多次。

这将带来一系列问题:

重复提交: 如果按钮用于提交表单,快速点击按钮可能导致表单被多次提交,这可能不是你所期望的行为。

性能问题: 如果按钮触发的是某个耗时的操作,比如发送网络请求,快速点击按钮可能导致不必要的性能开销。

这时候就要用到防抖或者节流。

防抖

通过设计防抖函数,可以确保在用户点击按钮后,只有在一定的时间间隔内才会触发相应的事件,比如在用户多次连续点击后,事件只触发一次。

代码实现

js 复制代码
<script>
    let btn = document.getElementById('btn');
    function send() {
        console.log('提交完成');
    }

    btn.addEventListener('click',debounce(send,1000))
    function debounce(fn,delay) {
        let timer;
        return function(){
            if (timer) clearTimeout(timer);
            timer = setTimeout(function () {
                fn()
            },delay)
        }
    }
</script>

代码分析

  1. 通过document.getElementById获取了id为'btn'的按钮元素,将其赋值给变量btn,以便后续的操作。
  2. function send() :这是一个用于处理点击事件的函数,用打印来替代实际事件。
  3. btn.addEventListener('click', debounce(send, 1000)):这里我们通过addEventListener方法为按钮添加了一个点击事件监听器。当按钮被点击时,会执行debounce(send, 1000),也就是使用了防抖的send函数。这里传递了两个参数,第一个是要执行的函数send,第二个是防抖的时间间隔,这里设置为1秒。
  4. function debounce(fn, delay) :这里我们创建防抖函数的函数。它接收两个参数,fn是需要防抖的函数,delay是防抖的时间间隔。
  5. debounce函数内部,定义了一个变量timer,用于前一次点击时存储的定时器。
  6. return function() :因为addEventListener接收的第二个参数是函数体,所以debounce需要函数返回新的函数,这个新函数就是实际上会被绑定到按钮点击事件上的函数。
  7. if (timer) clearTimeout(timer):这行代码表示如果已经存在一个定时器,说明上一次点击的事件还没有触发,所以清除掉这个定时器。
  8. timer = setTimeout():设置一个新的定时器,重新计算时间,如果一秒钟内没有再次点击,则事件才触发;如果一秒钟之内再次点击,则再次执行该函数。

当我们连续点击按钮的时候,控制台并不会打印,但是当我们停止点击,它在最后一次点击后一秒完成了打印。

效果展示

当我们连续点击,会以最后一个为基准,一秒后执行一次打印。

这样就实现了一个山寨版的防抖功能,何为山寨版?我们知道,在实际应用中,点击事件的触发的时候肯定会传递参数,那么怎么实现呢?

  1. 我们可以直接在返回的函数内接收这个参数,下面用e表示参数:
js 复制代码
<script>
    let btn = document.getElementById('btn');
    function send(e) {
        console.log('提交完成',e);
    }

    btn.addEventListener('click',debounce(send,1000))
    function debounce(fn,delay) {
        let timer;
        return function(e){
            if (timer) clearTimeout(timer);
            timer = setTimeout(function () {
                fn(e)
            },delay)
        }
    }
</script>

这样我们确实可以实现一个参数的传递,但是如果传递的参数不止一个,那这种方法就无济于事了。那该怎么实现呢?

在实现之前,我们得先认识arguments这个概念,它是函数内部的一个特殊对象,用于存储函数调用时传递的所有参数,它是一个类数组对象,具有类似数组的结构,但没有数组的一些方法。我们优化一下,可以得到以下代码:

js 复制代码
<script>
    //接收参数
    function send(e,a) {
        console.log('提交完成',e,a);
    }
    btn.addEventListener('click',debounce(send,1000))
    
    function debounce(fn,delay) {
        let timer;
        return function(){
            let args = arguments;
            if (timer) clearTimeout(timer);
            timer = setTimeout(()=> {
                fn.call(this,...args);
            },delay)
        }
    }
</script>

效果展示

成功传入参数。

  • function send(e):我们用这个函数接收一个参数e
  • let args = arguments;:在新函数内部,将传递给新函数的所有参数存储在变量args中。这是因为在防抖的场景下,我们希望能够传递原始的事件对象给实际执行的函数。
  • timer = setTimeout(() => {):设置一个新的定时器,用箭头函数使this指向外层函数函数(即return出来的函数)。
  • fn.call(this,...args):将thisfn函数绑定,并将解构出来的参数传递给要执行的函数。

以上,我们分别完成了不带参数与带参数的防抖函数,我们的主要思想就是,用一个定时器记录这一秒钟内,用户如果重复点击,则以最后一次点击为准,只打印一次。

节流

节流会在一定的时间间隔内固定执行函数,无论触发事件有多频繁,都会按照固定的频率执行。即使在这个时间间隔内有多次触发事件,也只会执行一次。

代码实现

html 复制代码
<script>
    //接收参数
    function send(e){console.log('提交了',e);}
    btn.addEventListener('click',throttle(send, 1000));
    function throttle(fn, delay) {
        let prevTime = Date.now();
        
        return function(){
            if(Date.now() - prevTime > delay){//第二次点击的时间 - 第一次点击的时间 > 1000
                fn.apply(this,arguments);
                prevTime = Date.now()
            }
        }
    }
</script>

效果展示

当我们不停点击时,打印会按照固定的频率执行,这里为一秒钟执行一次。

代码分析

前面的部分代码与防抖一致,这里就不过多赘述。

  1. let prevTime = Date.now();:在throttle函数内部,定义了一个变量prevTime,用于存储上一次函数执行的时间戳。
  2. if(Date.now() - prevTime > delay){:通过比较当前时间与上一次函数执行时间的差值,判断是否满足节流的条件,即是否超过了设定的时间间隔。这里我的想法是,如果第二次点击与第一次点击间隔大于1秒,则第二次点击允许执行,如果间隔小于1秒则不允许执行。
  3. prevTime = Date.now();:更新prevTime,记录下当前函数执行的时间戳,以备下一次判断使用。

在这个按钮事件中,我们实现了带参数的节流函数,我们的主要思想是,如果第二次点击与第一次点击间隔大于1秒,则第二次点击允许执行,如果间隔小于1秒则不允许执行。所以如果你不停点击按钮,它会按照固定的频率执行。

最后

综合来看,防抖和节流是前端开发中常用的两种优化手段,它们在处理函数执行频率方面都发挥着重要的作用。在实际项目中,根据具体的需求和交互场景选择合适的优化方式十分关键。所以面试官常常会叫你手写防抖或节流。

希望这篇文章对你有帮助!


我的Gitee: CodeSpace (gitee.com)

技术小白记录学习过程,有错误或不解的地方还请评论区留言,如果这篇文章对你有所帮助请 "点赞 收藏+关注" ,感谢支持!!

相关推荐
京东云开发者3 小时前
京东Taro Native框架静态布局直渲提速
前端
程序员小羊!3 小时前
03JavaScript预备知识
前端
前端的阶梯3 小时前
Cursor 开发 Python 项目完全指南
前端·人工智能·后端
半兽先生3 小时前
flv.js解决其中一个监控断线导致其他的监控播放阻塞
开发语言·javascript·ecmascript
艾伦野鸽ggg3 小时前
JavaScript 基础语法速通
前端·javascript
不懂的浪漫3 小时前
AI 时代还需要买课吗?我用 Skills + Markdown + HTML 搭了一套自学系统
前端·人工智能·html·skill
前端的阶梯3 小时前
Conda 开发 Python 程序完全指南
前端·人工智能·后端
zhengfei6113 小时前
第2章 Agent 核心组件深度解析
前端·javascript·react.js
Linsk3 小时前
前端代码压缩对浏览器兼容性的影响
前端
yingyima3 小时前
凌晨3点的闹钟:分布式定时任务设计实战
前端