【JavaScript教程】防抖(一):手搓一个防抖函数中的this ,闭包以及参数

零 JavaScript教程评论49字数 4506阅读15分1秒阅读模式

在JavaScript编程中,防抖是一个重要的概念,它能够优化事件处理器的性能。防抖函数确保在指定时间内,即使事件被触发多次,也只执行一次函数。这个技术在处理如窗口调整大小、滚动等频繁触发的事件时尤为有用。防抖函数的核心是闭包和`this`的理解。闭包允许函数访问并操作函数外部的变量,而`this`关键字则提供了一个对象的上下文引用。在实现防抖函数时,我们通常会创建一个延迟执行的函数,并在延迟期间如果再次触发事件,则重新设置计时器。这样,只有在最后一次事件触发后的延迟期满时,函数才会执行。参数的传递也是通过闭包来实现的,确保即使在异步执行时,也能够获得正确的参数值。

前言:

有必要了解一下防抖,防抖是一种常见的性能优化技术,通常用于限制频繁触发的事件(比如滚动、输入等)的执行次数,从而减少函数的执行次数,提高页面性能。通过使用防抖,可以有效避免在某些情况下频繁触发函数执行,比如输入框输入文字时立刻触发搜索或者请求数据,在滚动页面时触发某些操作等,从而减轻浏览器的负担,提高用户体验。文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/15330.html

防抖:在规定的时间内,没有下一次的触发,就执行文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/15330.html

高并发

同时有大量的请求或操作需要处理的情况,就是我点击一个按钮,反应点击的次数,随着我不间断的一直点击,后台需要不断的去处理导致服务器崩溃。文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/15330.html

xml

复制代码
<body>
    <button id="btn">提交</button>


    <script>
        let btn = document.getElementById('btn')
        btn.addEventListener('click',function() {
            console.log('提交');
        })
    </script>

</body>

设置一个提交按钮,通过 document 拿到点击按钮,通过 addEventListener 去监听点击事件,触发回调函数,返回打印的次数。文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/15330.html

image.png文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/15330.html

随着不断的点击提交按钮,这样就导致了后台的提交处理次数不断增多使其处理压力过大,所以这里可以通过防抖来处理。文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/15330.html

如果一直点击按钮,控制台就会一直输出,这里我们可以设置一个定时器,每隔 1 秒才再次触发回调函数,文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/15330.html

javascript

复制代码
 // 防抖函数
        function debounce(fn) {
            return function() {
                setTimeout(fn,1000)
            }
        }

此时btn绑定的监听事件是: btn.addEventListener('click',debounce(handle))文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/15330.html

但是实际的问题是,在一秒之内点击的次数不止一次而是N次,那么就会创建N个定时器,此时我们希望在你创建下一个定时器的时候同时销毁了上一个定时器,这样在你点了N多次后也会在隔一秒执行回调函数打印提交。文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/15330.html

javascript

复制代码

        // 防抖函数
        function debounce(fn) {
            let timer = null


            return function() {
                // 如果第二次的时间没到 1 S,就销毁上一次的定时器
                clearTimeout()
                timer = setTimeout(fn,1000)
            }
        }

这里诉说的不太好理解,附上完整代码,大家需要亲自动手实现效果才能更好明白:文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/15330.html

xml

复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <button id="btn">提交</button>


    <script>
        let btn = document.getElementById('btn')

        function handle() {
            // ajax 请求
            console.log('提交');
        }

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

        // 防抖函数
        function debounce(fn) {
            let timer = null


            return function() {
                // 如果第二次的时间没到 1 S,就销毁上一次的定时器
                clearTimeout()
                timer = setTimeout(fn,1000)
            }
        }




        // setTimeout(function() {
        //     console.log('setTimeout 触发了');
        // },2000)


    </script>

</body>
</html>

这就是基本的防抖效果,最核心的概念,其实这份代码也是最典型的闭包,去我的文章中再次熟悉一下 闭包的概念。

形成的闭包

这里的闭包是由防抖函数debounce形成的,因为字函数function的调用并不在debounce里面,而由于timer 的销毁调用形成闭包。子函数的调用先在自己的上下文词法环境中去找,找不到再去denounce留下的小包中找销毁上一次的定时器,这样在创建出最后一个定时器时,永远都会消除上一个定时器,就会腾出1s的时间来执行,从而达到了防抖的效果。

image.png

这里是不是可以不用闭包?可以,但是子函数的作用域就得写在群全局了,这里不再更改了,可以私聊我如何更改。

This的指向

xml

复制代码
    <script>
        let btn = document.getElementById('btn')

        function handle() {
            // ajax 请求
            console.log('提交',this);
        }

        btn.addEventListener('click',handle)

        // 防抖函数
        function debounce(fn) {
            let timer = null


            return function() {
                // 如果第二次的时间没到 1 S,就销毁上一次的定时器
                clearTimeout()
                timer = setTimeout(fn,1000)
            }
        }



        // setTimeout(function() {
        //     console.log('setTimeout 触发了');
        // },2000)


    </script>

This指向谁?这里This是handle的,但是由于addEventListener 帮你把handle触发了,所以This不指向window,而是指向这个函数的动物结构 ——> btn 。

image.png

在加上防抖函数效果的时候,This指向谁?

javascript

复制代码
 let btn = document.getElementById('btn')

        function handle() {
            // ajax 请求
            console.log('提交',this);
        }

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

        // 防抖函数
        function debounce(fn) {
            let timer = null


            return function() {
                // 如果第二次的时间没到 1 S,就销毁上一次的定时器
                clearTimeout()
                timer = setTimeout(fn,1000)
            }
        }

image.png

他是指向window的,这里只需要记住,定时器里面函数的回调就指向window

所以这里就导致了由于你防抖函数的执行改变了This的指向,无法指向想要执行的btn ,等于我帮你修好了冰箱却损坏了冰柜。

该如何解决This的指向问题?

javascript

复制代码
// 防抖函数
        function debounce(fn) {
            let timer = null

            
            return function() {
                // 如果第二次的时间没到 1 S,就销毁上一次的定时器
                clearTimeout()
                timer = setTimeout(fn,1000)
            }
        }

问题就是此时的 timer = setTimeout(fn,1000) 中的fn的指向错误如何给他摆正来?这里就涉及到了 This的显示绑定,有三个方法: call, bind,apply。 这里的This指向和绑定规则去参看我的文章来了解:This的条条框框

javascript

复制代码
// 防抖函数
        function debounce(fn) {
            let timer = null


            return function() {
                // 如果第二次的时间没到 1 S,就销毁上一次的定时器
                clearTimeout()
                timer = setTimeout(()=> {
                     fn.call(this)
                },1000) 
            }
        }

这里就是使用箭头函数,箭头函数里没有this这个机制,this为外层非箭头函数的this,所以fn中的this为返回的子函数的this,该函数是由btn来调用的,为this的隐式绑定,所以this指向btn,显示绑定强行将fn 的this执行function 相同的指向。

还有一个问题:事件参数 e

按钮的点击事件参数 e

xml

复制代码
    <script>
        let btn = document.getElementById('btn')

        function handle(e) {
            // ajax 请求
            console.log('提交',e);
        }

        btn.addEventListener('click',handle)

    </script>

handle 里面的事件参数给出了这样一个对象情况。

image.png

所以问题就是:我用了防抖函数之后,这个参数还会是这个对象吗?

xml

复制代码
<script>
        let btn = document.getElementById('btn')

        function handle(e) {
            // ajax 请求
            console.log('提交',e);
        }

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

        // 防抖函数
        function debounce(fn) {
            let timer = null


            return function() {

                // 如果第二次的时间没到 1 S,就销毁上一次的定时器
                clearTimeout()
                timer = setTimeout(()=> {
                    fn.call(this)
                },1000) 
            }
        }

    </script>

结果是undefined。也就是说把该给handle的东西又给到了别人。

image.png

所以该怎么交还?

通过taht 来实现

xml

复制代码
    <script>
        let btn = document.getElementById('btn')

        function handle(e) {
            // ajax 请求
            console.log('提交',e);
        }

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

        // 防抖函数
        function debounce(fn) {
            let timer = null


            return function(e) {
                const that = this
                // 如果第二次的时间没到 1 S,就销毁上一次的定时器
                clearTimeout(timer)
                timer = setTimeout(function() {
                    fn.call(that,e)
                },1000)
            }
        }
    </script>

image.png

OK!完美解决。

总结:防抖

梳理一下:

  1. debounce 返回一个函数体,跟debounce形成闭包
  2. 子函数体中每次先销毁上一个setTimeout,在创建一个新的setTimeout
  3. 还原 原函数的this的指向
  4. 还原 原函数的参数

这里内容比较精密,需要多去思考和理解。感谢支持!

零
  • 转载请务必保留本文链接:https://www.0s52.com/bcjc/javascriptjc/15330.html
    本社区资源仅供用于学习和交流,请勿用于商业用途
    未经允许不得进行转载/复制/分享

发表评论