likes
comments
collection
share

揭秘「防抖」技术,让每一次交互都恰到好处

作者站长头像
站长
· 阅读数 18

前言

在数字化洪流中,每一次点击、滚动、输入,都是对系统性能的考验。如何在海量交互中保持冷静,不让服务器心跳加速?答案就是——防抖技术!

正文

防抖的概念

防抖主要用于处理那些会触发大量、高频事件的操作,比如鼠标点击、窗口大小改变、输入框输入等。其核心思想是,在规定的时间内,如果某个事件被连续触发多次,只执行最后一次,前面的所有请求都会被忽略,直到规定时间结束后,才会执行一次。

防抖的基本原理

防抖的核心思想是:当第一次触发事件时,设置一个定时器,如果在这段时间内再次触发事件,则取消之前的定时器并重新设置一个新的定时器只有在最后一次触发事件之后的一段时间内没有新的触发,才会真正执行函数

防抖的使用

我们没有防抖的时候,如果一直点击下面的提交按钮,按钮就会被一直触发,很影响性能(我们假设输出内容是个ajax请求)

<body>
    <button id="btn">提交</button>
    <script>
        var btn = document.getElementById('btn');
        btn.addEventListener('click', () => {
            //ajax请求
           console.log('提交');
         })
    </script>
</body>

</html>

像这样:

揭秘「防抖」技术,让每一次交互都恰到好处

如果我们加上定时器:

let btn = document.getElementById('btn');
        //不行,点击几次就有几次计时函数
        btn.addEventListener('click', function() {
            //ajax请求
            setTimeout(() => {
                console.log('提交');
            }, 1000)
        })

揭秘「防抖」技术,让每一次交互都恰到好处

还是不行,只是提交的打印显示会延迟一秒出现。

现在我们就需要一个防抖函数

极简版

function debounce(fn) {
            let timer = null;
            //这是一个闭包(一个函数的内部函数被返回到函数外部去调用)
            return function () {
                //如果第二次的时间没到1s,就销毁上一次的计时器
                clearTimeout(timer)
                timer = setTimeout(fn, 1000)
            }
        }

这段代码的执行机制可以通过一个暴力点的比方理解:

有两个人,一个A一个B,AB起了争执,不想听B废话,B说你听我解释,A就给B一巴掌让它闭嘴( clearTimeout(timer))打断施法,B被打了还要说,直到A不打了,B才能完整解释

(防抖函数是A,要执行的函数是B)

揭秘「防抖」技术,让每一次交互都恰到好处

上面这段debounce 函数实现是可行的,但为了更完整和实用,我们通常还会在防抖函数内部保存 this 上下文和参数,以便在最终调用原始函数时能够正确地执行。下面来介绍更完整的版本:

完整版

在介绍之前我们要先了解一下下列代码中第4行的this

       var btn = document.getElementById('btn');
       
       function handle() {
            console.log('提交', this);
        }
        //点击一下就执行一次返回函数
        btn.addEventListener('click', handle)
        //防抖函数
        function debounce(fn) {
            let timer = null;
            //这是一个闭包
            return function () {
                //如果第二次的时间没到一秒就销毁上一次的计时器
                clearTimeout(timer)
                timer = setTimeout(fn, 1000)
            }
        }

thishandle()函数的,但是被addEventListener干扰了,导致this不指向全局window而指向buttonDOM元素

在 JavaScript 中,函数的 this 值取决于函数是如何被调用的,而不是它在哪里被定义的。当函数作为事件处理函数被调用时,this 的值通常指向触发事件的 DOM 元素。

handle不是独立调用的,是addEventListener触发的,所以this不指向window

揭秘「防抖」技术,让每一次交互都恰到好处

我们加上防抖,把第7行改成:

 btn.addEventListener('click', debounce(handle))

再执行:

揭秘「防抖」技术,让每一次交互都恰到好处

发现现在的this指向了window

为啥呢??

现在的handle()debounce()里的定时器调用的,而定时器里的回调函数的this默认指向window

那我们是希望这个this指向button的,防抖函数只防抖就行。

定时器里的this指向全局,但是debounce()里的this还是指向debounce(),所以我们可以通过使用 fn.call(this) 来显式地绑定 fn 函数的 this

        function debounce(fn) {
            let timer = null;
            
            return function () {
               const that = this
                clearTimeout(timer)
                timer = setTimeout(function() {
                    fn.call(that)
                }, 1000)
            }
        }

揭秘「防抖」技术,让每一次交互都恰到好处

带参数版

任何一个事件在触发的时候都会接收一个事件对象作为参数

这个对象包含了有关事件的所有信息,比如事件的类型、触发事件的目标元素、事件的坐标、按键状态等等

所以handle()也会带一个参数e

 function handle(e) {
            console.log('提交', e);
        }

在不防抖的情况下,执行结果:

揭秘「防抖」技术,让每一次交互都恰到好处

那如果咱们加上防抖呢?

揭秘「防抖」技术,让每一次交互都恰到好处

得,又搞丢了一个东西,因为事件对象 e 在外层被忽略了,所以 handle 函数无法接收到它。

function debounce(fn) {
            let timer = null;
            return function (e) {
            const that = this

             clearTimeout(timer)
             timer = setTimeout(() => {
                  fn.call(that,e)
              }, 1000)
           }
        }

第8行代码通过在返回的函数中接受参数 e,并在内部的 setTimeout 回调中使用 fn.call(that, e),确保事件对象能够被正确地传递给最终的事件处理器函数 fn

总结

  1. 形成闭包: debounce 返回一个内部函数,该函数可以访问 debounce 的局部变量,如计时器。
  2. 管理计时器: 内部函数每次调用时清除现有计时器并设置新计时器,确保只在最后调用后延迟执行。
  3. 保持 this 上下文: 保存并还原原始函数的 this 指向,确保在正确上下文中执行。
  4. 传递参数: 确保原始函数可以接收到所有调用时传递的参数。

以上就是本篇文章全部内容,防抖技术不仅是一套编程技巧,更是前端工程师在用户体验与性能优化之间找到的平衡点。希望本文对你有所帮助,感谢你的阅读!

转载自:https://juejin.cn/post/7389925417787539493
评论
请登录