防抖、节流(闭包,this指向)

698 阅读3分钟

一、什么是防抖,为什么使用到防抖?

防抖:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
场景:搜索框处理,在需要用到搜索查询的地方,一般都是使用input框加上oninput事件进行处理(获取到值,根据值去请求后端),再比如说:针对滚动条,用户滑动滚动条时,当停止滑动时,便触发某个事件,这一类的情况都可以用到防抖

举个例子:

<body>
    <input type="text">
    <script>
        window.onload = function () {
            let inp = document.querySelector('input')
            inp.oninput = function () {
                    console.log(this.value);
            }
        }

    </script>
</body>
/**
*这里有一个知识点,this的指向,在事件内部的this指向是绑定该事件的元素,这里是input绑定了
*oninput事件,那么这里的this指向的就是input框,所以this.value可以拿到输入的值
*/

image.png

可以看到,输入好好学习这四个字,这段代码就执行了12次,如果说在输入时需要发送请求呢?执行一次就发送一个请求?那这样一来,输入的关键词太长,请求可能会执行几十上百次,这样一来服务器和浏览器的压力会很大,对用户也是非常不友好的,基于这样的场景,防抖的出现,成功解决了这样的一个问题!

防抖(debounce)

<body>
    <input type="text">
    <script>
        window.onload = function () {

            let inp = document.querySelector('input')
            let t = null
            inp.oninput = function (e) {
                if (t != null) {
                    clearTimeout(t)
                }
                t = setTimeout(function () {
                    console.dir(this.value);
                }.bind(this), 1000)
            }
        }

    </script>
</body>
/*
*定时器的this指向的是全局的window,那么this.value是取不到input值的,我们可以改变this的指向,从而取到值
*bind()的作用类似call和apply,都是修改this指向。但是call和apply是修改this指向后函数会
立即执行,而bind则是返回一个新的函数,它会创建一个与原来函数主体相同的新函数,新函数中的
this指向传入的对象。
*在这里为什么不能用call和apply,是因为call和apply不是返回函数,而是立即执行函数,那么,就失去了定时器的作用。
*
*/

image.png

这里可以看到输入好好学习只执行了一次,这就是防抖

接下来对防抖函数进行封装

<body>
    <input type="text">
    <script>
        window.onload = function () {
            let inp = document.querySelector('input')
            inp.oninput = debounce(function () {
                // 业务代码
                console.log(this.value);
            }, 1000)
            function debounce(fn, delay) {
                let t = null
                return function () {
                    if (t != null) {
                        clearTimeout(t)
                    }
                    t = setTimeout(function () {
                        fn.call(this)
                    }.bind(this), delay)
                }
            }
        }
    </script>
</body>
/*
*大致逻辑是这样的:我们封装时需要考虑业务代码和算法分开,所以oninput事件触发的函数应该在函数内部添加
业务代码,所以防抖函数传如一个回调函数,而我们需要的是延时指向业务代码,所以setTimeout中可以直接执行
传入的fn,为了更加灵活,将时间通过参数的方式传递进来
*在setTimeout的参数函数使用bind改变this的指向,使得指向事件对象,然而我们调用debounce传入的函数的
this指向的是window,所以在调用fn时在用call再一次改变this的指向,使其指向事件对象,就可以拿到value
*这里同时使用到了闭包,debounce中return出去的函数就是一个闭包
*在给input绑定事件的时候没有使用箭头函数,由于箭头函数是没有this的,所以这里使用箭头函数,this指向
在window,无法获取值
*/

debounce(优化)

<body>
    <input type="text">
    <script>
        window.onload = function () {
            let inp = document.querySelector('input')
            inp.oninput = debounce(function() {
                // 业务代码
                console.log(this.value);
            }, 1000)
            function debounce(fn, delay) {
                let t = null
                return function () {
                    if (t) clearTimeout(t)
                    t = setTimeout(()=> {
                        fn.call(this)
                    }, delay)
                }
            }
        }
    </script>
</body>

二、什么是节流,为什么使用到节流?

节流:连续触发事件但是在 n 秒中只执行一次函数。节流会稀释函数的执行频率
场景:节流同样适用于input搜索,根据业务需要,使用节流防抖均可,在鼠标移动触发某些事件或者滚动触发,请求某些内容,使用节流会更加友好。

举个例子:

<body>
    <script>
        window.onscroll = function () {
            if (flag) {
                setTimeout(() => {
                    console.log('滚动就会执行');
                }, 500)
            }
        }
    </script>
</body>

image.png

可以看到滚动到末尾,此时没有使用节流,共执行了45次,我们想要的只是在滚动期间,每隔多少事件间隔触发某种事件,并不需要每次都触发,那么这样的一个场景就可以使用到节流了

节流(throttle)

<body>
    <script>
        let flag = true
        window.onscroll = function () {
            if (flag) {
                setTimeout(() => {
                    console.log('滚动就会执行');
                    flag = true
                }, 500)
            }
            flag = false
        }
    </script>
</body>

image.png

这里可以看到,滚动到末尾只触发了2次,这就是节流

throttle(优化)

<body>
    <script>
        window.onscroll = throttle(function () {
            // 业务代码
            console.log('滚动就会执行');
        }, 500)
        function throttle(fn, delay) {
            let flag = true
            return function () {
                if (flag) {
                    setTimeout(() => {
                        fn.call(this)
                        flag = true
                    }, delay)
                }
                flag = false
            }
        }
    </script>
</body>

这里节流和防抖封装方法一致,可以参考防抖中的注释,更加容易理解节流