WithCoderWithCoderWithCoder

简介 js 防抖和节流

    前端 js 日常开发中,在页面鼠标移动 mousemove,或窗口的 resize、scroll,输入框内容校验等高频操作时,如果事件处理函数调用的频率无限制,会加重浏览器的负担,如果处理不当或放任不管很容易引起浏览器卡死,导致用户体验非常差。此时我们可以采用 debounce(防抖)和 throttle(节流)的方式来减少调用频率,同时又不影响实际效果。 

    一、 函数防抖

    函数防抖(debounce):当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次;如果设定的时间到来之前,又一次触发了事件,就重新开始延时。

    以鼠标移动为例,当鼠标移动时,计算鼠标当前的位置。通常情况下,我们的代码如下:

<script>
    function handleMousemove(args) {
        console.log("鼠标位置,X:" + args.clientX + ",Y: " + args.clientY);
    }

    document.body.onmousemove = handleMousemove;
</script>

    实际运行时,我们会发现,只要鼠标移动,控制台就会不停的输出结果。这样的执行频率太高了,然而我们并不需要这么高的执行频率。此时,就可以使用防抖来减少无意义的执行。

    思路如下:在第一次触发事件时,不会立即执行处理函数,而是给出一个延时期限(如1秒)。如果在1秒延时内,没有再次触发事件,则执行处理函数;如果在1秒延时时间内,再次触发事件,则取消当前的计时,重新开始计时。这样,在短时间内大量触发同一事件,最后只会执行一次。

    具体示例代码如下(用到js闭包和setTimeout):

<script>
    var args = null;
    
    // 防抖
    // fn : 需要防抖的函数
    // delay : 毫秒,防抖计时
    function debounce(fn, delay) {
        var timer = null;
        return function () {
            args = arguments[0];
            if (timer) { // 进入该分支,说明有一个正在计时的处理,然后又再次触发了相同的事件
                clearTimeout(timer);
            }
            timer = setTimeout(fn, delay); // 重新开始计时(若指定时间delay内没有再次触发相同事件,将执行处理函数fn)
        }
    }
    
    function handleMousemove() {
        console.log("鼠标位置,X:" + args.clientX + ",Y: " + args.clientY);
    }
    
    document.body.onmousemove = debounce(handleMousemove, 1000);
</script>

    运行以上代码,当鼠标持续移动,触发 mousemove 事件时,事件处理函数 handleMousemove 只在停止移动1000毫秒之后才会调用一次;而在持续移动鼠标,触发 mousemove 事件的过程中,事件处理函数 handleMousemove 一直没有执行。

    对于函数防抖,如果事件一直持续触发(如鼠标在屏幕上不停移动),只要不停止触发,理论上是不会执行处理函数。不过,如果想要在事件触发的过程中,每隔固定的时间,执行一次事件处理函数,就要用到函数节流。

    二、函数节流

    函数节流(throttle):当持续触发事件时,保证一定时间段内只调用一次事件处理函数。节流通俗解释就比如给草坪浇水,阀门一打开,就开始放水;不过我们不能一直让水留着,最好是按照固定的时间间隔(如1天)自动开关。

    函数节流主要有两种实现方法:时间戳和定时器。

    接下来仍以鼠标移动为例,每隔1秒,计算鼠标当前位置。分别用两种方法实现节流。

    2.1 节流throttle代码(时间戳):

<script>
    // 节流
    // fn : 需要节流的函数
    // delay : 毫秒,执行事件处理间隔
    function throttle(fn, delay) {
        var lastTime = Date.now();
        
        return function () {
            var args = arguments[0];
            var curTime = Date.now();
            if (curTime - lastTime >= delay) {
                fn(args);
                lastTime = curTime;
            }
        }
    }
    
    function handleMousemove(args) {
        console.log("鼠标位置,X:" + args.clientX + ",Y: " + args.clientY);
    }
    
    document.body.onmousemove = throttle(handleMousemove, 2000);
</script>

    以上代码,当 mousemove 事件触发时,在超过指定时间间隔(2秒)后再次触发事件,会执行第一次事件处理函数;而后如果持续频繁地触发事件,也都是每超过delay时间(2秒)执行一次。

    2.2 节流throttle代码(定时器):

<script>
   // 节流
    // fn : 需要节流的函数
    // delay : 毫秒,执行事件处理间隔
    function throttle(fn, delay) {
        var timer = null;
        
        return function () {
            var args = arguments[0];
            if (!timer) {
                timer = setTimeout(function () {
                    fn(args);
                    timer = null;
                }, delay);
            }
        }
    }
    
    function handleMousemove(args) {
        console.log("鼠标位置,X:" + args.clientX + ",Y: " + args.clientY);
    }
    
    document.body.onmousemove = throttle(handleMousemove, 2000);
</script>

    以上代码,当触发事件的时候,我们设置一个定时器,直到delay时间(2秒)后,定时器执行事件处理函数,并且清空定时器,这样就可以设置下个定时器。当第一次触发事件时,不会立即执行函数,而是在delay秒(2秒)后才执行。而后如果持续频繁地触发事件,也都是每delay时间(2秒)执行一次。当最后一次停止触发后,由于定时器的delay延迟,可能还会执行一次事件处理函数。

    总结

    函数防抖:将多次操作合并为一次操作进行。原理是维护一个计时器,规定在delay时间后触发函数,但是如果在delay时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。

    函数节流:使得一定时间内只触发一次函数。原理是通过判断是否到达一定时间来触发函数。函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数。

欢迎分享交流,转载请注明出处:WithCoder » 简介 js 防抖和节流