小陈同学の前端笔记 | 来简单了解下函数防抖

290 阅读3分钟

「这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战

模拟场景

在日常开发过程中,经常会遇到频繁触发的操作,例如页面的scroll,鼠标的mousemove,键盘的keydown等等。比如在百度搜索框内输入关键字的时候,下方都会出现相关的联想词,来预测用户想要搜索的内容,我们每次敲入文字的时候都会频繁地触发。

为了更直观地体现频繁触发,我写了一段简单的代码。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>防抖</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        body {
            height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
        }

        .box {
            width: 300px;
            height: 300px;
            text-align: center;
            line-height: 300px;
            background-color: gray;
            color: #fff;
            font-size: 30px;
        }
    </style>
</head>
<body>
    <div class="box"></div>
    <script>
        let box = document.querySelector('.box')
        let num = 0

        function addNum(){
            box.innerHTML = num++
        }
        
        box.addEventListener('mousemove',addNum)
    </script>
</body>
</html>

eg3kb-gel3p.gif

从动图中我们可以发现,随着鼠标的移动,数字会频繁地增长。当然这正是我们这段程序想要的效果,针对于数字的变化,这或许消耗不了多少的性能,但如果是频繁的网络请求,那这将会出现卡顿。

为了解决这个问题,我们可以采用防抖节流

今天我们就先来介绍防抖

什么是防抖

所谓防抖,就是在触发事件n秒后再执行函数,如果在n秒内又触发了事件,则会重新计算函数执行时间。

还是以刚才的鼠标移动为例,假设我们设定的时间为1s,如果我们触发了鼠标移动事件且在1s内继续移动鼠标,那么不会执行函数,数字不会继续增加;如果我们停止触发事件,那么1s后,就会执行函数,数字会增加。

下面我们来实现防抖

防抖的实现

非立即执行版本

function debounce(func,time){
    let timer
    return function(){
        if(timer)
            clearTimeout(timer)
        timer = setTimeout(func,time)
    }
}

使用方法如下:

box.addEventListener('mousemove',debounce(addNum,1000))

s3kgv-p3br1.gif

我们发现已经实现了想要的效果,在1s内继续触发事件则不会执行函数,而是重新计时,当我们停止触发事件的1秒后执行函数。但这样其实存在一个问题,即第一次触发事件的时候也不会执行函数。

我们需要做一些简单的修改。

立即执行版本

function debounceImmediate(func,time){
    let timer
    return function(){
        if(timer)
            clearTimeout(timer)
        const callNow = !timer
        timer = setTimeout(()=>{
            timer = null
        },time)
        // 第一次触发事件的时候,还没有定时器,因此会执行一次函数
        if(callNow)
            func()
    }
}

5u2mu-e53g4.gif

我们可以给防抖函数加入一个形参immediate作为判断,若为true则为立即执行版本。

最终版本

虽然我们已经实现了防抖的效果,如果直接调用debounce函数的话,会导致函数addNum中的this指向window,且没法附带原有的参数。

因此以下是防抖函数的最终版本 (immediate形参、改变this、带有参数)

function debounceFinal(func,delay,immediate) {
    let timer;
    return function(){
        let context = this
        let args = arguments
        if(timer) clearTimeout(timer)
        if(immediate){
            const callNow = !timer
            timer = setTimeout(() => {
                timer = null
            }, delay);
            if(callNow) func.apply(context,args)
        }else{
            timer = setTimeout(() => {
                func.apply(context,args)
            }, delay);   
        }
    }
}

调用addNum函数

function addNum(e){
    // 新增了this和e作为测试
    console.log(this)
    console.log(e)
    box.innerHTML = num++
}

box.onmousemove = debounceFinal(addNum,1000,true)

控制台输出如下图所示。

微信截图_20211109204803.png

结语

以上就是防抖函数的介绍以及实现的代码,下期我们再来介绍一下另一种方案——节流。

如有纰漏,欢迎各位指出!