面试官:手写一个防抖

前言

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

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

应用场景

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

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)

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

相关推荐
webmote24 分钟前
Fabric.js 入门教程:扩展自定义对象的完整实践(V6)
运维·javascript·canvas·fabric·绘图
YRr YRr33 分钟前
全国硕士研究生入学考试(考研)常识详解之复试考试科目:笔试、面试与加试
考研·面试·职场和发展
新中地GIS开发老师1 小时前
25考研希望渺茫,工作 VS 二战,怎么选?
javascript·学习·考研·arcgis·地理信息科学·地信
萧大侠jdeps2 小时前
Vue 3 与 Tauri 集成开发跨端APP
前端·javascript·vue.js·tauri
HEU_firejef2 小时前
面试经典 150 题——数组/字符串(一)
数据结构·算法·面试
码农爱java2 小时前
设计模式--装饰器模式【结构型模式】
java·设计模式·面试·装饰器模式·原理·23 中设计模式
绝无仅有2 小时前
gozero项目日志收集与配置实战
后端·面试·架构
uhakadotcom2 小时前
2025年,最新的AI发展趋势是什么?
后端·面试·架构
JYeontu2 小时前
实现一个动态脱敏指令,输入时候显示真实数据,展示的时候进行脱敏
前端·javascript·vue.js
发呆的薇薇°2 小时前
react里使用Day.js显示时间
前端·javascript·react.js