美文网首页
节流模式

节流模式

作者: 做最棒的 | 来源:发表于2019-06-28 21:03 被阅读0次

    一、节流模式(Throttler)

    对重复的业务逻辑进行节流控制,并执行最后一次操作并取消其它操作,以提高性能。

    二、节流原理

    • 如果你持续触发事件,每隔一段时间,只执行一次事件。
    • 根据首次是否执行以及结束后是否执行,效果有所不同,实现的方式也有所不
      同。我们用 leading 代表首次是否执行,trailing 代表结束后是否再执行一次。
    • 关于节流的实现,有两种主流的实现方式,一种是使用时间戳,一种是设置定时器。
    1、时间戳方法

    让我们来看第一种方法:使用时间戳,当触发事件的时候,我们取出当前的时间戳,然后减去之前的时间戳(最一开始值设为 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;
            }
        }
    }
    

    演示图片


    2、使用定时器

    当触发事件的时候,我们设置一个定时器,再触发事件的时候,如果定时器存在,就不执行,直到定时器执行,然后执行函数,清空定时器,这样就可以设置下个定时器。

    // 第二版
    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)
            }
    
        }
    }
    

    演示图片


    3、两种方式的比较
    • 第一种事件会立刻执行,第二种事件会在 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;
    }
    

    演示图片


    四、优化

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

    那我们设置个 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;
    }
    

    五、取消

    // 第五版 非完整代码,完整代码请查看最后的演示代码链接

    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
    });
    

    七、loadsh throttle 源码解析

    var debounce = require('./debounce'),
        isObject = require('./isObject');
    
    /** Error message constants. */
    var FUNC_ERROR_TEXT = 'Expected a function';
    
    /**
     * Creates a throttled function that only invokes `func` at most once per
     * every `wait` milliseconds. The throttled function comes with a `cancel`
     * method to cancel delayed `func` invocations and a `flush` method to
     * immediately invoke them. Provide `options` to indicate whether `func`
     * should be invoked on the leading and/or trailing edge of the `wait`
     * timeout. The `func` is invoked with the last arguments provided to the
     * throttled function. Subsequent calls to the throttled function return the
     * result of the last `func` invocation.
     *
     * **Note:** If `leading` and `trailing` options are `true`, `func` is
     * invoked on the trailing edge of the timeout only if the throttled function
     * is invoked more than once during the `wait` timeout.
     *
     * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
     * until to the next tick, similar to `setTimeout` with a timeout of `0`.
     *
     * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
     * for details over the differences between `_.throttle` and `_.debounce`.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Function
     * @param {Function} func The function to throttle.
     * @param {number} [wait=0] The number of milliseconds to throttle invocations to.
     * @param {Object} [options={}] The options object.
     * @param {boolean} [options.leading=true]
     *  Specify invoking on the leading edge of the timeout.
     * @param {boolean} [options.trailing=true]
     *  Specify invoking on the trailing edge of the timeout.
     * @returns {Function} Returns the new throttled function.
     * @example
     *
     * // Avoid excessively updating the position while scrolling.
     * jQuery(window).on('scroll', _.throttle(updatePosition, 100));
     *
     * // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes.
     * var throttled = _.throttle(renewToken, 300000, { 'trailing': false });
     * jQuery(element).on('click', throttled);
     *
     * // Cancel the trailing throttled invocation.
     * jQuery(window).on('popstate', throttled.cancel);
     */
    function throttle(func, wait, options) {
      var leading = true,
          trailing = true;
    
      if (typeof func != 'function') {
        throw new TypeError(FUNC_ERROR_TEXT);
      }
      if (isObject(options)) {
        leading = 'leading' in options ? !!options.leading : leading;
        trailing = 'trailing' in options ? !!options.trailing : trailing;
      }
      return debounce(func, wait, {
        'leading': leading,
        'maxWait': wait,
        'trailing': trailing
      });
    }
    
    module.exports = throttle;
    
    

    相关文章

      网友评论

          本文标题:节流模式

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