防抖函数还不懂?进来2分钟让你搞懂!

206 阅读3分钟

JS函数防抖

触发事件后,在 n 秒内函数只能执行一次,如果触发事件后在 n 秒内又触发了事件,则会清除上一次时间重新计算函数延执行时间。

举栗子

我们都知道声控灯,声控灯是当有声音出现的时候灯亮起几秒钟,几秒钟后自动熄灭。但是如果你在亮起这几秒钟内再发出声音,那么声控灯就会重新计算熄灭时间。假设声控灯亮起间隔为4s,如果你在第2s的时候发出声音,那么声控灯就会将熄灭时间重新改为4s而不是在剩余熄灭的时间叠加4s。我们函数防抖也是这个道理


看栗子

正常

<body>
    <input id="btn" type="button" value="快快点我">
</body>

<script>
    let btn = document.querySelector('#btn');
    btn.addEventListener('click',function(){
        console.log(123);
    })
</script>

GIF.gif

防抖

<body>
    <input id="btn" type="button" value="快快点我">
</body>

<script>
    let btn = document.querySelector('#btn');

    function debounce(fn, delay = 5000) {
        let timer = null;

        return function () {
            if (timer) {
                clearTimeout(timer);
            }
            timer = setTimeout(() => {
                fn.apply(this, arguments);
                timer = null
            }, delay)
        }
    }

    btn.addEventListener('click', debounce(() => {
        console.log(123);
    }, 1000))
</script>

GIF-1640082950227.gif

前端开发过程中,有一些事件,常见的例如,onresizescrollmousemove ,mousehover 等,会被频繁触发(短时间内多次触发),不做限制的话,有可能一秒之内执行几十次、几百次,如果在这些函数内部执行了其他函数,尤其是执行了操作 DOM 的函数(浏览器操作 DOM 是很耗费性能的),那不仅会浪费计算机资源,还会降低程序运行速度,甚至造成浏览器卡死、崩溃。这种问题显然是致命的。


学栗子

在分析防抖函数核心代码之前我们得先看看这个

let btn = document.querySelector('#btn');

function clickEvent(){
    console.log("我被点击了");
}
btn.addEventListener('click', clickEvent())

你觉得会发生什么事?点击一下按钮就输出一次内容?但事实并非如此,当运行时会直接输出"我被点击了",后续的点击按钮没有任何反应。

为什么? 因为addEventListener第二个参数需要的是一个函数,而你却进行了函数调用

上面的情况相当于:

function TestEvent(fn) {
    console.log(typeof fn);	// undefined
    fn();	// 类型异常,fn不是函数
}

TestEvent(function s() {
    console.log(123);
}() )

// Console Print

// 123
// undefined
// TypeError: fn is not a function

我们再看看这个

function clickEvent(){
    return function(){
        console.log("我被点击了");
    }

}
btn.addEventListener('click', clickEvent())

你是否会觉得跟上面发生的事情一样 —— 直接输出内容按钮点击无效。但并不是。因为clickEvent函数内部返回了一个函数。你需要先调用它才能返回函数。

好了,当你搞清楚这些东西后我们掌握防抖函数就是洒洒水了。

第一步:创建一个防抖函数

function debounce(){

}

第二步:接收需要防抖的函数和延迟的时间

function debounce(fn, delay){

}

第三步:防抖核心代码

function debounce(fn, delay) {
    let timer = null;   

    if (timer) {    // 如果当前存在定时器直接关闭掉
        clearTimeout(timer);
    }

    timer = setTimeout(() => {  // 开启一个定时器
        fn()    

        timer = null;
    }, delay)
}

整体测试一下

<body>
    <input id="btn" type="button" value="快快点我">
    <script>
        let btn = document.querySelector('#btn');

        function debounce(fn, delay) {
            let timer = null;   

            if (timer) {    // 如果当前存在定时器直接关闭掉
                clearTimeout(timer);
            }

            timer = setTimeout(() => {  // 开启一个定时器
                fn()    

                timer = null;
            }, delay)
        }

        btn.addEventListener('click', debounce(() => {
            console.log(11);
        }, 1000))
    </script>
</body>

当我们喜出望外的去查看成果时,不出意外出意外了,哈哈,发现出现了我们之前出现的情况 —— 直接输出内容按钮点击无效,只不过这里是延迟了1s输出内容。

为什么会这样呢? 我们之前讲过addEventListener第二个参数需要的是一个函数,而我们这里进行了函数调用。

你听到这里可能就直接跟我急眼了。"你逗我玩是吧,我不用调用我怎么传函数进去"

放下你手中的刀稍安勿躁,还记得我们之前说过的函数内部返回一个函数,我们修改一下代码:

function debounce(fn, delay) {
    let timer = null;

    return function () {
        if (timer) {    // 如果当前存在定时器直接关闭掉
            clearTimeout(timer);
        }

        timer = setTimeout(() => {  // 开启一个定时器
            fn()

            timer = null;
        }, delay)
    }

}

// 调用
debounce(()=>{
    console.log(123)
},1000)()

我们去掉后面的括号让addEventListener帮我们调用,整体代码:

<body>
    <input id="btn" type="button" value="快快点我">
    <script>
        let btn = document.querySelector('#btn');

        function debounce(fn, delay) {
            let timer = null;

            return function () {
                if (timer) {    // 如果当前存在定时器直接关闭掉
                    clearTimeout(timer);
                }

                timer = setTimeout(() => {  // 开启一个定时器
                    fn()

                    timer = null;
                }, delay)
            }

        }

        btn.addEventListener('click', debounce(() => {
            console.log(11);
        }, 1000))
    </script>
</body>