面试官:手写一个防抖

前言

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

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

应用场景

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

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)

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

相关推荐
浮华似水23 分钟前
简洁之道 - React Hook Form
前端
正小安2 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
小飞猪Jay4 小时前
C++面试速通宝典——13
jvm·c++·面试
_.Switch4 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光4 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   4 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   4 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web4 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常4 小时前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式
莹雨潇潇5 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器