js的节流实现

throttle

function getUserAction(e) { 
	...
};

container.onmousemove = debounce(getUserAction, 1000);
  • 第一版本,使用时间戳, 利用闭包保存previous的值,记录上一次运行的时间戳,与当前时间相差超过 wait 毫秒则运行
    // 第一版
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;
        }
    }
}

  • 第二版本,利用定时器,如果定时器存在,就不执行,直到定时器执行,然后执行函数,清空定时器,这样就可以设置下个定时器。
// 第二版
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)
        }

    }
}

  • 前面两种之间的差异性
  • 第一种会立即执行函数,第二种需要在wait毫秒后才第一次执行。
  • 第一种事件停止触发后没有办法再执行事件,第二种事件停止触发后依然会再执行一次事件。

  • 第三版本
// 第三版
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;
}

  • 第四版,增加条件控制,options参数 { leading: boolean, trailing: false },
  • 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();
        //首次触发 options.leading 为false,由于时间戳,会触发不了
        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;
}

  • 如果同时设置的话,比如当你将鼠标移出的时候,因为 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
});

代码例子:https://github.com/linzl008/debounceAndThrottle.git


版权声明:本文为lin819397746原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。