js函数节流(非setTimeout方案)

2019年08月31日Web前端

只记得js函数节流可以使用setTimeout的方案,那么有没有其他的方案呢?

时间戳

思路:每次触发的时候与前一次触发时刻作比较,超过时间则触发,没有则等待下一次触发。

function throttle(fn, delay) {
    let prev = 0;

    return function() {
        let now = +new Date(),
            context = this;

        if (now-prev >= delay) {
            fn.apply(context, arguments);
            previous = now;
        }
    }
}

首先,一开始会触发下,接下的过程中,每次对比触发时间。

定时器版

setTimeout方案可以查看函数去抖动和节流。

大致如下:

function throttle(fn, delay) {
    let timer = null;

    return function() {
        let context = this,
            args = arguments;
        if(!timer) {
            timer = setTimeout(function() {
                fn.apply(context, args);
                timer = null;
            }, delay);
        }
    }
}

两者区别

时间戳的方式,会提前先执行一遍,等后期有连续调用时再检测时间做触发判断。

定时器的方式,调用后等待相应时间后再触发,也就是说,当调用停止后,也会触发最后一次。

两者结合

underscore中提供了throttle方法,是将两者结合的方式,删掉些无关紧要的东西,如下:

function throttle(func, wait) {
    var timeout, context, args, result;
    var previous = 0;
    // 延时函数
    var later = function() {
        previous = +new Date();
        timeout = null;
        result = func.apply(context, args);
        if (!timeout) context = args = null;
    };
    var throttled = function() {
        var now = +new Date();
        var remaining = wait - (now - previous); // 剩余时间
            context = this;
            args = arguments;
        if (remaining <= 0 || remaining > wait) {
            // 超出时间,直接触发,清除原先的定时器
            // 这块属于纠正,当出现修改pc时间这种问题也能处理掉,
            if (timeout) {
                clearTimeout(timeout);
                timeout = null;
            }
            // 记录时间戳
            previous = now;
            // 触发处理
            result = func.apply(context, args);
            if (!timeout) context = args = null;
        } else if (!timeout) {
            // 正常的延时触发
            timeout = setTimeout(later, remaining);
        }
        return result;
    };
    // 节流取消
    throttled.cancel = function() {
        clearTimeout(timeout);
        previous = 0;
        timeout = context = args = null;
    };
    return throttled;
};

我们可以只关注throttled函数,其中remaining的方式较好的处理了多种异常情况,例如,

  • setTimeout需要前一次时间循环的宏任务执行完后才会运行的偏差,此时表现为remaining值小于0。
  • 修改pc时间这种问题,会提前执行方法,表现为remaining值大于wait。

underscore中的原方法还支持设置options为{leading: false},可以关掉首次函数调用触发的问题。

地址:underscore.js,左侧也提供了简单的代码说明。