likes
comments
collection
share

理解js防抖和节流只需要这一篇文章(详细到每一行代码的实现和使用场景)

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

一、防抖

  • 防抖:在事件被触发n秒后再执行回调函数,如果在这n秒内又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。

代码实现

我们分析防抖的概念,首先声明防抖函数debounce;

    function debounce(){}

我们有如下一个常见需求,获取用户在input框中的值。

    <input type="test" oninput="getInputValue(event)">
    
    <script>
    function getInputValue (event){
        console.log(event.target.value)
    }
    </script>

getInputValue()这个函数就能实时的拿到用户在input中输入的值,但是有一个弊端,只要用户输入了值就会马上触发这个方法,同时会多次回调。比如用户输入1234,那么这个方法就会触发四次,打印四次console,对于console来说确实不怎么消耗性能,但是我们如果要把这个数据用作后台的ajax请求交互,比如常见的搜索功能,那么就非常的消耗性能了,上述例子来讲,打印四次console,就相当于四次ajax请求,这是非常没有必要的。所以此时就是我们防抖就有大作用了。

有了上述的使用场景,我们继续来讲设计思路,还是以上诉为例,我们可以这么去做,我们在获取input中的值的时候以1s为基准,如果用户在1s内输入了1234,那么我们就在1s后再去读取输入的值,然后把数据给后端。如果用户输入1234用了两秒,那么我们可以先判断1s内用户是否还在输入,发现用户还在输入我们再等1s,然后读取一次。然后再把数据给后台,依次类推。当然我们还可以以2s,3s为基准,这都是可以的。

结合上面的思路我们来实现防抖。

  1. 一个时间基准:wait;一种操作:fn;(上述的ajax请求后台阶交互)
    function debounce(fn,wait){}

2.上述例子中1秒后读取值,时间基准读取值,我门可以使用js中的setTimeout()延时函数

    function debounce(fn,wait){
        setTimeout(()=>{
            fn()
        },wait)
    }

3.如果用户在基准时间内还在进行操作,那么我门要延长获取数据的时间。在实现这个这个功能时我们需要具备闭包的知识。同时要知道setTimeout()是有返回值的,它会返回一个序列号。我们可以使用clearTimeout(number)马上清除setTimeout()的延时,number就是setTimeout()的序号返回值。

3.1 我们再细一点。首先实现我们实现延时的清除。

    function debounce(fn,wait){
        let timer;
        if(timer){ //这里判断是否有延时序列号,有才清除。
            clearTimeout(timer)
        }
        timer = setTimeout(()=>{
            fn()
        },wait)  //这里上面有讲,setTimeout有返回值,是序列好,clearTimeout传入序列号,可清除响应的延时。
    }

3.2 好了,到这里我们只差最后一步啦。现在我们把之前的需求实现一下,看看有什么问题。

    <input type="test" oninput="getInputValue(event)">
    
    <script>
        function debounce(fn,wait){
            let timer;
            if(timer){ 
                clearTimeout(timer)
            }
            timer = setTimeout(()=>{
                fn()
            },wait)
        }
        const ajax = debounce((val)=>{
            console.log('使用val做后台交互')
        },500)
        
        function getInputValue (event){
            ajax(event.target.value)
        }
    </script>

走一遍上述的代码会发现,我们毎调用一次ajax函数,就是调用debounce,然后每次都会生成一个timer;timer始终是undefined;始终不会走clearTimeout;没有达到我们要求,

解决办法,我们可以使用必包啦。我们让debounce有一个返回值,返回一个函数。timer(闭包会继承外部函数的AO)类似一个标记。然后我们再看以下代码,最后我们的调用的参数别搞忘了;

    <input type="test" oninput="getInputValue(event)">
    
    <script>
        function debounce(fn,wait){
            let timer;
            return (...args) => {
                if(timer){ 
                    clearTimeout(timer)
                }
                timer = setTimeout(()=>{
                    fn(...args)
                },wait)
            }
          
        }
        const ajax = debounce((val)=>{
            console.log('使用val做后台交互')
        },500)
        
        function getInputValue (event){
            ajax(event.target.value)
        }
    </script>

最后得到我们的防抖函数

    function debounce (fn,wait) {
        let timer;
        return (...args) => {
            if(timer){
                clearTimeout(timer)
            }
            timer = setTimeout(() => {
                fn(...args)
            },wait)
        }
    }

二、节流

  • 节流:规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。

代码实现;

上面我们详细的讲解了防抖,节流也是一样的逻辑,区别在与,防抖会清除计数器,节流多一个状态。我们直接附上代码

    function throttle (fn,wait) {
        let isWaiting = false
        return (...args) => {  //这里还是用闭包,上面有原因
            if (isWaiting){
                return
            }
            fn(...args);
            isWaiting = true;
            setTimeout(() => {
                isWaiting = false
            },wait)
        }   
    }

从上面的代码我们其实就发现,节流就是加一个等待的延时器,只有当延时器执行,将isWaiting变成false后才能执行fn;