节流

作者: 幻城之雪 | 来源:发表于2023-04-27 07:34 被阅读0次

    为什么要限制事件的频繁触发,以及如何做限制:

    1.debounce 防抖

    2.throttle 节流

    今天重点讲讲节流的实现。

    节流

    节流的原理很简单:

    如果你持续触发事件,每隔一段时间,只执行一次事件。

    根据首次是否执行以及结束后是否执行,效果有所不同,实现的方式也有所不同。

    我们用 leading 代表首次是否执行,trailing 代表结束后是否再执行一次。

    关于节流的实现,有两种主流的实现方式,一种是使用时间戳,一种是设置定时器。

    使用时间戳

    让我们来看第一种方法:使用时间戳,当触发事件的时候,我们取出当前的时间戳,然后减去之前的时间戳(最一开始值设为 0 ),如果大于设置的时间周期,就执行函数,然后更新时间戳为当前的时间戳,如果小于,就不执行。

    // 第一版
    
    function throttle(func, wait) {
    
        var context, args;
    
        var previous = 0;
    
        return function() {
    
            var now = +new Date();
    
            context = this;
    
            args = arguments;
    
            if (now - previous > wait) {
    
                func.apply(context, args);
    
                previous = now;
    
            }
    
        }
    
    }
    
    container.onmousemove = throttle(getUserAction, 1000);
    

    我们可以看到:当鼠标移入的时候,事件立刻执行,每过 1s 会执行一次,如果在 4.2s 停止触发,以后不会再执行事件。

    使用定时器

    接下来,我们讲讲第二种实现方式,使用定时器。当触发事件的时候,我们设置一个定时器,再触发事件的时候,如果定时器存在,就不执行,直到定时器执行,然后执行函数,清空定时器,这样就可以设置下个定时器。

    // 第二版
    
    function throttle(func, wait) {
    
        var timeout;
    
        var previous = 0;
    
        return function() {
    
            context = this;
    
            args = arguments;
    
            if (!timeout) {
    
                timeout = setTimeout(function(){
    
                    timeout = null;
    
                    func.apply(context, args)
    
                }, wait)
    
            }
    
        }
    
    }
    

    我们可以看到:当鼠标移入的时候,事件不会立刻执行,晃了 3s 后终于执行了一次,此后每 3s 执行一次,当数字显示为 3 的时候,立刻移出鼠标,相当于大约 9.2s 的时候停止触发,但是依然会在第 12s 的时候执行一次事件。

    所以比较两个方法:

    • 第一种事件会立刻执行,第二种事件会在 n 秒后第一次执行

    • 第一种事件停止触发后没有办法再执行事件,第二种事件停止触发后依然会再执行一次事件

    双剑合璧

    那我们想要一个什么样的呢?

    有人就说了:我想要一个有头有尾的!就是鼠标移入能立刻执行,停止触发的时候

    还能再执行一次!

    所以我们综合两者的优势,然后双剑合璧,写一版代码:

    // 第三版
    
    function throttle(func, wait) {
    
        var timeout, context, args, result;
    
        var previous = 0;
    
        var later = function() {
    
            previous = +new Date();
    
            timeout = null;
    
            func.apply(context, args)
    
        };
    
        var throttled = function() {
    
            var now = +new Date();
    
            //下次触发 func 剩余的时间
    
            var remaining = wait - (now - previous);
    
            context = this;
    
            args = arguments;
    
            // 如果没有剩余的时间了或者你改了系统时间
    
            if (remaining <= 0 || remaining > wait) {
    
                if (timeout) {
    
                    clearTimeout(timeout);
    
                    timeout = null;
    
                }
    
                previous = now;
    
                func.apply(context, args);
    
            } else if (!timeout) {
    
                timeout = setTimeout(later, remaining);
    
            }
    
        };
    
        return throttled;
    
    }
    

    我们可以看到:鼠标移入,事件立刻执行,晃了 3s,事件再一次执行,当数字

    变成 3 的时候,也就是 6s 后,我们立刻移出鼠标,停止触发事件,9s 的时

    候,依然会再执行一次事件。

    优化

    但是我有时也希望无头有尾,或者有头无尾,这个咋办?

    那我们设置个 options 作为第三个参数,然后根据传的值判断到底哪种效果,

    我们约定:

    leading:false 表示禁用第一次执行

    trailing: false 表示禁用停止触发的回调

    我们来改一下代码:

    // 第四版
    
    function throttle(func, wait, options) {
    
        var timeout, context, args, result;
    
        var previous = 0;
    
        if (!options) options = {};
    
        var later = function() {
    
            previous = options.leading === false ? 0 : new Date().getTime();
    
            timeout = null;
    
            func.apply(context, args);
    
            if (!timeout) context = args = null;
    
        };
    
        var throttled = function() {
    
            var now = new Date().getTime();
    
            if (!previous && options.leading === false) previous = now;
    
            var remaining = wait - (now - previous);
    
            context = this;
    
            args = arguments;
    
            if (remaining <= 0 || remaining > wait) {
    
                if (timeout) {
    
                    clearTimeout(timeout);
    
                    timeout = null;
    
                }
    
                previous = now;
    
                func.apply(context, args);
    
                if (!timeout) context = args = null;
    
            } else if (!timeout && options.trailing !== false) {
    
                timeout = setTimeout(later, remaining);
    
            }
    
        };
    
        return throttled;
    
    }
    

    取消

    在 debounce 的实现中,我们加了一个 cancel 方法,throttle 我们也加个 cancel 方法:

    // 第五版 非完整代码,完整代码请查看最后的演示代码链接
    
    ...
    
    throttled.cancel = function() {
    
        clearTimeout(timeout);
    
        previous = 0;
    
        timeout = null;
    
    }
    
    ...
    

    注意

    我们要注意 underscore 的实现中有这样一个问题:

    那就是 leading:false 和 trailing: false 不能同时设置。 如果同时设置的话,比如当你将鼠标移出的时候,因为 trailing 设置为false, 停止触发的时候不会设置定时器,所以只要再过了设置的时间,再移入的话,就会 立刻执行,就违反了 leading: false,bug 就出来了,所以,这个throttle 只有三种用法:

    container.onmousemove = throttle(getUserAction, 1000);
    
    container.onmousemove = throttle(getUserAction, 1000, {
    
        leading: false
    
    });
    
    container.onmousemove = throttle(getUserAction, 1000, {
    
        trailing: false
    
    });
    

    至此我们已经完整实现了一个 underscore 中的 throttle 函数,恭喜,撒花!

    相关文章

      网友评论

        本文标题:节流

        本文链接:https://www.haomeiwen.com/subject/zofbjdtx.html