简单的油门在js

Mia*_*Mia 56 javascript jquery

我在JS寻找一个简单的油门.我知道像lodash和下划线这样的库有它,但只有一个函数包含任何这些库都是过度的.

我还在检查jquery是否有类似的功能 - 找不到.

我找到了一个工作油门,这是代码:

function throttle(fn, threshhold, scope) {
  threshhold || (threshhold = 250);
  var last,
      deferTimer;
  return function () {
    var context = scope || this;

    var now = +new Date,
        args = arguments;
    if (last && now < last + threshhold) {
      // hold on to it
      clearTimeout(deferTimer);
      deferTimer = setTimeout(function () {
        last = now;
        fn.apply(context, args);
      }, threshhold);
    } else {
      last = now;
      fn.apply(context, args);
    }
  };
}
Run Code Online (Sandbox Code Playgroud)

这个问题是:它在节流时间结束后再次激活该功能.所以我们假设我在按键时每隔10秒发一个油门 - 如果我按下2次按键,它会在10秒钟完成时触发第二次按键.我不想要这种行为.

Clé*_*ost 64

我会使用underscore.jslodash源代码来查找此函数的经过良好测试的版本.

下面是下划线代码的略微修改版本,用于删除对underscore.js本身的所有引用:

// 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.
function throttle(func, wait, options) {
  var context, args, result;
  var timeout = null;
  var previous = 0;
  if (!options) options = {};
  var later = function() {
    previous = options.leading === false ? 0 : Date.now();
    timeout = null;
    result = func.apply(context, args);
    if (!timeout) context = args = null;
  };
  return function() {
    var now = Date.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;
  };
};
Run Code Online (Sandbox Code Playgroud)

请注意,如果您不需要所有支持下划线的选项,则可以简化此代码.

编辑1:删除了对下划线的另一个引用,thx到Zettam的评论

编辑2:添加了关于lodash和可能的代码简化的建议,thx to lolzery wowzery的评论

  • 对我来说并不简单.这是一个简单的[好例子](https://jsfiddle.net/jonathansampson/m7G64/) (32认同)
  • 之所以不像链接到@vsync那样简单,原因之一是因为它支持尾随调用。对于此函数,如果您两次调用了结果函数,则将导致对包装函数的两次调用:一次调用,一次在延迟之后。在与之链接的一个vsync中,它将导致单个立即调用,但在延迟后没有响应。在许多情况下,接收尾随调用对于获取最后的视口大小或您要执行的操作非常重要。 (5认同)
  • 实际上,这并不简单.但它是生产就绪和开源的. (4认同)
  • 请不要使用它.我不打算自大.相反,我只是想要实用.这个答案比它需要的更复杂.我发布了一个单独的答案来解决这个问题所做的所有这些以及更少的代码行. (2认同)
  • @Nico总是在不是箭头函数的任何函数中定义“ arguments”对象:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/arguments (2认同)

sma*_*use 20

那这个呢?

function throttle(func, timeFrame) {
  var lastTime = 0;
  return function () {
      var now = new Date();
      if (now - lastTime >= timeFrame) {
          func();
          lastTime = now;
      }
  };
}
Run Code Online (Sandbox Code Playgroud)

简单的。

您可能有兴趣查看源代码

  • 对我来说,这只适用于“Date.now()”而不是“new Date()” (4认同)
  • 这是页面上最干净的最小实现。 (3认同)
  • @Vic 不能保证最后一次调用你的函数 (3认同)

Vik*_*sal 12

callback:接受应该调用的函数

limit:应在时间限制内调用该函数的次数

time:重置限制计数的时间跨度

功能和用法:假设您有一个API,允许用户在1分钟内调用它10次

function throttling(callback, limit, time) {
    /// monitor the count
    var calledCount = 0;

    /// refresh the `calledCount` varialbe after the `time` has been passed
    setInterval(function(){ calledCount = 0 }, time);

    /// creating a closure that will be called
    return function(){
        /// checking the limit (if limit is exceeded then do not call the passed function
        if (limit > calledCount) {
            /// increase the count
            calledCount++;
            callback(); /// call the function
        } 
        else console.log('not calling because the limit has exceeded');
    };
}
    
//////////////////////////////////////////////////////////// 
// how to use

/// creating a function to pass in the throttling function 
function cb(){
    console.log("called");
}

/// calling the closure function in every 100 milliseconds
setInterval(throttling(cb, 3, 1000), 100);
Run Code Online (Sandbox Code Playgroud)

  • @lolzerywowzery这不像你的答案没有它需要的那么复杂 (6认同)
  • 请不要在生产代码中使用此答案。这是糟糕的编程的极好选择。假设您的网页上有1000个按钮(听起来可能很多,但请三思而后行:按钮无处不在:在弹出窗口,子菜单,面板等中),并希望每个按钮最多每200秒触发一次。它们很可能同时每333毫秒(或每秒3次)同时启动,因此所有这些计时器都需要再次检查时,会出现巨大的延迟尖峰。这个答案完全滥用了setInterval,其目的并非本意。 (4认同)
  • 我建议像这样触发回调:`callback(...arguments)` 以保留原始参数。非常便利 (2认同)

小智 10

这是一个与vsync的解决方案相同的限制函数,添加了将参数传递给限制函数.

function throttle (callback, limit) {

  var wait = false;
  return function () {
    if (!wait) {

      callback.apply(null, arguments);
      wait = true;
      setTimeout(function () {
        wait = false;
      }, limit);
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

可以像这样使用:

window.addEventListener('resize', throttle(function(e){console.log(e)}, 100));
Run Code Online (Sandbox Code Playgroud)

在没有addEventListener上下文的情况下使用:

throttle(function(arg1, arg2){console.log(arg1, arg2);}, 100)('red', 'blue');
// red blue
Run Code Online (Sandbox Code Playgroud)

  • 这是一个相当便宜的去抖动,而您很可能错过了最后一个事件。将其绑定到keydown并非常快速地按一些键,肯定会错过最重要的最后一个。 (4认同)

vip*_*yal 8

下面是我在 9LOC 的 ES6 中实现油门功能的方法,希望对你有帮助

function throttle(func, delay) {
  let timeout = null
  return function(...args) {
    if (!timeout) {
      timeout = setTimeout(() => {
        func.call(this, ...args)
        timeout = null
      }, delay)
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

单击此链接以查看它是如何工作的。

  • @JackGiffin:使用传播并不是不合适的;没有什么限制节流函数只能用于事件处理程序。 (4认同)
  • 简单但相当无效:即使在不合适的情况下也会延迟功能,并且不会保持待处理事件的新鲜度,从而导致用户交互滞后的潜在重大滞后。此外,使用 `...` 展开语法是不合适的,因为只有 1 个参数被传递给事件侦听器:事件对象。 (2认同)

Div*_*ani 7

添加到这里的讨论(以及更近的游客),如果不使用的原因,几乎实际上throttlelodash是有一个小尺寸的包或捆绑,则有可能只包括throttle在你的包,而不是整个lodash库。例如在ES6中,它将类似于:

import throttle from 'lodash/throttle';
Run Code Online (Sandbox Code Playgroud)

此外,还有一个throttle只能从封装lodash称为lodash.throttle其可以用简单的使用import在ES6或require在ES5。

  • 检查代码。它使用2个文件导入,因此这意味着您需要3个文件才能使用简单的节流功能。我要说的有点过大,特别是如果某人(例如我自己)需要约200行代码程序的调节功能。 (3认同)
  • 是的,它内部使用`debounce`和`isObject`,整个捆绑包大小约为[2.1KB最小](https://bundlephobia.com/result?p=lodash.throttle@4.1.1)。我想,对于一个小程序没有意义,但是我更喜欢在较大的项目中使用它,而不是创建自己的节流功能,我也必须进行测试:) (3认同)

aki*_*uri 7

我只需要一个用于调整窗口大小事件的节流/去抖动功能,出于好奇,我还想知道这些是什么以及它们是如何工作的。

我已经阅读了多篇关于 SO 的博客文章和 QA,但它们似乎都过于复杂,建议使用库,或者只提供描述而不是简单的普通 JS 实现。

我不会提供描述,因为它很丰富。所以这是我的实现:

function throttle(callback, delay) {
    var timeoutHandler = null;
    return function () {
        if (timeoutHandler == null) {
            timeoutHandler = setTimeout(function () {
                callback();
                timeoutHandler = null;
            }, delay);
        }
    }
}

function debounce(callback, delay) {
    var timeoutHandler = null;
    return function () {
        clearTimeout(timeoutHandler);
        timeoutHandler = setTimeout(function () {
            callback();
        }, delay);
    }
}
Run Code Online (Sandbox Code Playgroud)

这些可能需要调整(例如,最初不会立即调用回调)。

查看操作的差异(尝试调整窗口大小):

function throttle(callback, delay) {
    var timeoutHandler = null;
    return function () {
        if (timeoutHandler == null) {
            timeoutHandler = setTimeout(function () {
                callback();
                timeoutHandler = null;
            }, delay);
        }
    }
}

function debounce(callback, delay) {
    var timeoutHandler = null;
    return function () {
        clearTimeout(timeoutHandler);
        timeoutHandler = setTimeout(function () {
            callback();
        }, delay);
    }
}
Run Code Online (Sandbox Code Playgroud)
function throttle(callback, delay) {
    var timeoutHandler = null;
    return function () {
        if (timeoutHandler == null) {
            timeoutHandler = setTimeout(function () {
                callback();
                timeoutHandler = null;
            }, delay);
        }
    }
}

function debounce(callback, delay) {
    var timeoutHandler = null;
    return function () {
        clearTimeout(timeoutHandler);
        timeoutHandler = setTimeout(function () {
            callback();
        }, delay);
    }
}

var cellDefault  = document.querySelector("#cellDefault div");
var cellThrottle = document.querySelector("#cellThrottle div");
var cellDebounce = document.querySelector("#cellDebounce div");

window.addEventListener("resize", function () {
    var span = document.createElement("span");
    span.innerText = window.innerWidth;
    cellDefault.appendChild(span);
    cellDefault.scrollTop = cellDefault.scrollHeight;
});

window.addEventListener("resize", throttle(function () {
    var span = document.createElement("span");
    span.innerText = window.innerWidth;
    cellThrottle.appendChild(span);
    cellThrottle.scrollTop = cellThrottle.scrollHeight;
}, 500));

window.addEventListener("resize", debounce(function () {
    var span = document.createElement("span");
    span.innerText = window.innerWidth;
    cellDebounce.appendChild(span);
    cellDebounce.scrollTop = cellDebounce.scrollHeight;
}, 500));
Run Code Online (Sandbox Code Playgroud)
table {
    border-collapse: collapse;
    margin: 10px;
}
table td {
    border: 1px solid silver;
    padding: 5px;
}
table tr:last-child td div {
    width: 60px;
    height: 200px;
    overflow: auto;
}
table tr:last-child td span {
    display: block;
}
Run Code Online (Sandbox Code Playgroud)

JSFiddle

  • @commonSenseCode “甚至不起作用”是什么意思?我已经提供了演示代码。它显然有效。请尝试更详细一点。无论什么不起作用,我很确定它与您的实施有关。 (4认同)

rsi*_*imp 7

我在这里看到了很多答案,对于“js 中的简单节流”来说太复杂了。

几乎所有更简单的答案都只是忽略“节流”中的调用,而不是将执行延迟到下一个间隔。

这是一个简单的实现,也可以处理“节流中”的调用:

const throttle = (func, limit) => {
  let lastFunc;
  let lastRan = Date.now() - (limit + 1); //enforces a negative value on first run
  return function(...args) {
    const context = this;
    clearTimeout(lastFunc);
    lastFunc = setTimeout(() => {
      func.apply(context, args);
      lastRan = Date.now();
    }, limit - (Date.now() - lastRan)); //negative values execute immediately
  }
}
Run Code Online (Sandbox Code Playgroud)

这与简单去抖动的实现几乎完全相同。它只是添加了超时延迟的计算,需要跟踪函数上次运行的时间。见下文:

const debounce = (func, limit) => {
  let lastFunc;
  return function(...args) {
    const context = this;
    clearTimeout(lastFunc);
    lastFunc = setTimeout(() => {
      func.apply(context, args)
    }, limit); //no calc here, just use limit
  }
}
Run Code Online (Sandbox Code Playgroud)