防抖和节流

  在很多网站我们都可以看到回到顶部功能,实现原理很简单,就是监听scroll事件,一旦触发scroll事件就获取scrollTop的值,当scrollTop的值超过一定的数字就显示回到顶部按钮。
 
  这种实现方式有个性能缺陷,就是事件触发频率非常高,高到什么程度我们可以通过以下代码测试:
 
<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title>scrollTop测试</title>
    <style type="text/css">.demo{width:100vw;height:300vh;background:#cce8cf;}</style>
    <script type="text/javascript">
        window.onscroll = function () {
            // scrollTop就是触发滚轮事件时滚轮的高度(为了保证兼容性,这里取两个值,哪个有值取哪一个)
            var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
            console.log('滚动条位置:' + scrollTop);
        }
    </script>
</head>
<body>
<div class="demo"></div>
</body>
</html>
 
  通过实际测试我们可以知道,每次按下“↓”键会触发scroll事件8~9次,但是实际上我们不需要这么高频的操作,这时我们可以通过某种方式使得短时间内的scroll事件的处理函数只执行一次,这种方式就叫做防抖,实现防抖的代码很简单:

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title>scrollTop测试</title>
    <style type="text/css">.demo{width:100vw;height:300vh;background:#cce8cf;}</style>
    <script type="text/javascript">
        /**
         * 防抖函数
         *
         * @param string func     要使用防抖功能的函数名
         * @param int    millisec 调用函数间隔时间,单位:毫秒
         * @return void
         */
        function debounce(func, millisec) {
            var timeout = null;
            return function () {
                if (timeout !== null) {
                    clearTimeout(timeout);
                }
                timeout = setTimeout(func, millisec);
            }
        }

        function printScrollTop() {
            // scrollTop就是触发滚轮事件时滚轮的高度(为了保证兼容性,这里取两个值,哪个有值取哪一个)
            var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
            console.log('滚动条位置:' + scrollTop);
        }

        window.onscroll = debounce(printScrollTop, 1000); // 原生JavaScript方式
        // $(window).scroll(debounce(printScrollTop, 1000)); // 使用jQuery方式
    </script>
</head>
<body>
<div class="demo"></div>
</body>
</html>
 
  使用防抖机制来监听scroll事件大幅减少了操作,使浏览器的性能压力大幅降低。
 
  从上面的代码可以看出,防抖实际上就是把处理事件函数放到setTimeout()里以达到延迟执行的效果,如果第一个事件处理还没完成第二个就来了,那么第一个事件处理将会被清除不执行,同理如果第二个事件处理还没完成第三个就来了,那么第二个事件处理将也会被清除不执行,这时如果过了延迟执行时间第四个事件处理还没来那么第三个事件处理就会执行,所以最终效果就是只有最后一个事件处理会被执行。
 
  从上面的描述我们可以发现防抖有个副作用,以上面的代码为例,如果用鼠标按着滚动条不放手并不断上下拖来拖去,那么scroll事件的处理函数将永远不会执行,对应的bug就是即便滚动条拖到了底部也不会显示回到顶部按钮。为了解决这个问题我们需要将代码改进一下,使得用户不松开滚动条也能执行scroll事件的处理函数,代码如下:
 
<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title>scrollTop测试</title>
    <style type="text/css">.demo{width:100vw;height:300vh;background:#cce8cf;}</style>
    <script type="text/javascript">
        /**
         * 节流函数
         *
         * @param string func     要使用节流功能的函数名
         * @param int    millisec 调用函数间隔时间,单位:毫秒
         * @return void
         */
        function throttle(func, millisec) {
            var canRun = true;
            return function () {
                if (!canRun) return;
                canRun = false;
                setTimeout(function () {
                    func();
                    canRun = true;
                }, millisec);
            };
        }

        function printScrollTop() {
            // scrollTop就是触发滚轮事件时滚轮的高度(为了保证兼容性,这里取两个值,哪个有值取哪一个)
            var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
            console.log('滚动条位置:' + scrollTop);
        }

        window.onscroll = throttle(printScrollTop, 1000);
    </script>
</head>
<body>
<div class="demo"></div>
</body>
</html>
 
  上面的实现方式就叫做节流。
 
  防抖和节流都是通过使用setTimeout()来延迟执行事件处理函数并结合算法实现减少执行次数,两者的区别在于,在高频事件被触发后,防抖只会执行最后一次事件处理,而节流会每隔一段时间就执行一次,从防抖有clearTimeout()而节流没有clearTimeout()也可以看出两者的区别。
 
  在高频事件里,节流机制每隔一段时间就执行一次处理函数,感觉有点像是使用定时器来实现,虽然使用定时器也能实现相同的功能,但无疑节流机制更好,因为节流机制比定时器更优秀之处在于它是依赖事件触发的,而不是像定时器的无条件执行。
 
  防抖除了应用在监听滚动条还常用在监听文本框,比如实现联想词、即时显示搜索结果等,尤其是即时显示搜索结果,由于需要向服务器请求,如果不使用防抖会让前端向服务器发送很多没有意义的请求,并且响应速度跟不上触发频率还会出现延迟、假死或卡顿的现象。

Copyright © 2024 码农人生. All Rights Reserved