创建自己的节流函数并使用 setTimeout 进行测试

Creating own throttle function and testing with setTimeout

我有一个任务要编写自己的油门函数。它需要使用setTimeout通过一定数量的测试。

这是我的代码:

var throttle = function(func, delay) {
  var counter = 0;
  var calledOnce = false;
  setInterval(function(){
    counter++;
  }, 1);
  return function() {
    if (counter > delay || !calledOnce) {
      calledOnce = true;
      counter = 0; 
      return func.apply(this, arguments);
    }
  };
};

我正在使用以下内容对其进行测试:

var callBack = function () {
  console.log('called');
};

var func = throttle(callback, 1000);

func();                     // should be called
setTimeout(func, 500);      // should not be called
setTimeout(func, 1000);     // should be called
setTimeout(func, 1500);     // should not be called
setTimeout(func, 1900);     // should not be called

然而,当我 运行 我的代码就是这里的样子时,该函数只被调用一次,使用原始的 func() 调用,none setTimeout 中的函数正在被调用。

我的代码或使用 setTimeout 进行的测试是否有任何明显的问题?

你的代码有什么问题:

  1. setIntervalsetTimeout 相比计算量大。
  2. 您在单线程上的测试 运行 部署代码时会出现这种情况吗? - 不,我想不是。您可以记录调用函数的确切时间以及调用的线程。
  3. 当您有高负载或多个线程检查它们是否应该执行时,我认为您会遇到 时间膨胀

仅仅因为您以 1 毫秒的间隔将其设置为 运行 并不意味着浏览器会这样做。例如,如果我将它设置为 0 以强制它采用尽可能小的间隔并多次执行此操作以获得平均值,我发现我可以使用的最小间隔是 ~6 毫秒。在重负载的情况下,这会显着增加。

var start = new Date();
var i = 0, interval = setInterval(function(){
    if (++i >= 1000) {
        var end = new Date();
        var result = (end-start)/1000;
        $('#result').text("The average interval was "
                          +result+" milliseconds");
        $('#browser').text(navigator.userAgent);
        clearInterval(interval);
    }
}, 0);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<p id=result>Please wait about 10 to 20 seconds ...</p>
<p id=browser></p>

它是如何完成的

不算自己写的,但是annotated underscore source throttle function :

Returns a function, that, when invoked, will only be triggered at most once during a given window of time. Normally, the throttled function will run as much as it can, without ever going more than once per wait duration; but if you’d like to disable the execution on the leading edge, pass {leading: false}. To disable execution on the trailing edge, ditto.

  _.throttle = function(func, wait, options) {
    var context, args, result;
    var timeout = null;
    var previous = 0;
    if (!options) options = {};
    var later = function() {
      previous = options.leading === false ? 0 : _.now();
      timeout = null;
      result = func.apply(context, args);
      if (!timeout) context = args = null;
    };
    return function() {
      var now = _.now();
      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;
        result = func.apply(context, args);
        if (!timeout) context = args = null;
      } else if (!timeout && options.trailing !== false) {
        timeout = setTimeout(later, remaining);
      }
      return result;
    };
  };

来自http://underscorejs.org/#throttle

throttle_.throttle(function, wait, [options]) 

Creates and returns a new, throttled version of the passed function, that, when invoked repeatedly, will only actually call the original function at most once per every wait milliseconds. Useful for rate-limiting events that occur faster than you can keep up with.

By default, throttle will execute the function as soon as you call it for the first time, and, if you call it again any number of times during the wait period, as soon as that period is over. If you'd like to disable the leading-edge call, pass {leading: false}, and if you'd like to disable the execution on the trailing-edge, pass {trailing: false}.

var throttled = _.throttle(updatePosition, 100);
$(window).scroll(throttled);

你需要清除间隔,因为无论你将间隔设置为零,它们仍然会增加计数器,间隔仍然有效,如果你想调用其他函数,而不仅仅是设置 false calledOnce。

也许这段代码可以帮到你。


var throttle = function(func, delay) {
  var counter = 0;
  var calledOnce = false;
  var count = function() {
    counter++;
    if(counter > delay) {
      clearInterval(handle);
      handle=0;
    }
  };
  var handle = setInterval(count, 1);
  return function() {
    if (!calledOnce) {
      calledOnce = false;
      counter = 0; 
      return func.apply(this, arguments);
    }
  };
};

这会起作用:

var throttle = function (input, delay) {
    var counter = 0;
    var calledOnce = false;
    var prev = Date.now();

    return function () {
        var now = Date.now();
        counter = now - prev;
        if (counter > delay || !calledOnce) {
            calledOnce = true;
            counter = 0;
            prev = now;
            return input.apply(null);
        }
    };
};

一个像样的油门函数不需要大量的局部变量。节流功能的目的是减少浏览器资源,而不是应用过多的开销以致您使用的更多。作为这一说法的证据,我设计了一个在其范围内只有 3 个 "hanging" 变量的节流函数。 ("hanging" 变量是一个永远不会被垃圾收集的变量,因为它总是被一个可能 可能 调用的函数引用,从而占用内存。)请注意。

var timenow = self.performance?performance.now.bind(performance):Date.now;
function throttle(func, minInterval) {
    var lastTimeWent = -minInterval;
    return function() {
        var newTimeWent = timenow();
        if ((newTimeWent-lastTimeWent) > minInterval) {
            lastTimeWent = newTimeWent;
            return func.apply(this, arguments);
        }
    };
}

使用您的示例进行测试:

(function(){"use strict";
var timenow = self.performance?performance.now.bind(performance):Date.now;
function throttle(func, minInterval) {
    var lastTimeWent = -minInterval;
    return function() {
        var newTimeWent = timenow();
        if ((newTimeWent-lastTimeWent) > minInterval) {
            lastTimeWent = newTimeWent;
            return func.apply(this, arguments);
        }
    };
}
var callBack = function () {
    console.log('\tcalled!');
};
var func = throttle(callBack, 1000);

var start = timenow();
wrapper();                     // should be called
setTimeout(wrapper, 500);      // should not be called
setTimeout(wrapper, 1000);     // should be called
setTimeout(wrapper, 1500);     // should not be called
setTimeout(wrapper, 1900);     // should not be called
async function wrapper(){
    console.log("Calling after " + Math.round(timenow()-start) + "ms...");
    func();
    console.log("Done Calling!");
}
})();

默认情况下,这会将函数限制为每 200 毫秒最多调用一次。要将间隔更改为不同的毫秒数,请在选项参数中传递一个名为 "interval" 的键并将其设置为所需的毫秒数。