Подтвердить что ты не робот

Простой дроссель в js

Я ищу простой газ в JS. Я знаю, что такие библиотеки, как lodash и underscore, есть, но только для одной функции было бы излишне включать любую из этих библиотек.

Я также проверял, имеет ли 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);
    }
  };
}

Проблема в том, что она запускает функцию еще раз по истечении времени газа. Итак, давайте предположим, что я нажал на газ, который срабатывает каждые 10 секунд при нажатии клавиш - если я нажму 2 раза, он все равно сработает при втором нажатии клавиш после 10 секунд. Я не хочу такого поведения.

4b9b3361

Ответ 1

Я бы использовал исходный код underscore.js или lodash, чтобы найти хорошо протестированную версию этой функции.

Вот немного измененная версия кода подчеркивания, чтобы удалить все ссылки на сам 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;
  };
};

Обратите внимание, что этот код может быть упрощен, если вам не нужны все опции, которые подчеркивают поддержку.

Редактировать 1: Удалена еще одна ссылка на подчеркивание, спасибо комментарию Zettam

Редактировать 2: Добавлено предложение о lodash и возможном упрощении кода, спасибо в комментарии lolzery wowzery

Ответ 2

обратный вызов: принимает функцию, которая должна быть вызвана

limit: сколько раз эта функция должна вызываться в течение срока

время: промежуток времени для сброса счетчика ограничений

функциональность и использование. Предположим, у вас есть API, который позволяет пользователю вызывать его 10 раз за 1 минуту

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

Ответ 3

Вот дроссельная функция, которая идентична решению vsync с добавлением передачи аргументов в дроссельную функцию.

function throttle (callback, limit) {

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

      callback.apply(null, arguments);
      wait = true;
      setTimeout(function () {
        wait = false;
      }, limit);
    }
  }
}

Можно использовать так:

window.addEventListener('resize', throttle(function(e){console.log(e)}, 100));

Использовать без контекста addEventListener:

throttle(function(arg1, arg2){console.log(arg1, arg2);}, 100)('red', 'blue');
// red blue

Ответ 4

В дополнение к обсуждению здесь (и для более недавних посетителей), если причина, по которой не используется почти фактический throttle из lodash, состоит в том, чтобы иметь меньшую по размеру упаковку или пакет, тогда можно включить только throttle в ваш пакет вместо всей библиотеки lodash. Например, в ES6 это будет что-то вроде:

import throttle from 'lodash/throttle';

Также существует пакет throttle только из lodash с именем lodash.throttle, который можно использовать с простым import в ES6 или require в ES5.

Ответ 5

Вот как я реализовал функцию дроссельной заслонки в ES6 в 9LOC, надеюсь, что она поможет

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

Нажмите на ссылку , чтобы узнать, как она работает.

Ответ 6

Мне просто нужна была функция throttle/debounce для события изменения размера окна, и, любопытно, я также хотел знать, что это такое и как они работают.

Я читал несколько постов в блоге и тесты на SO, но все они, кажется, слишком усложняют это, предлагают библиотеки или просто предоставляют описания, а не простые простые реализации JS.

Я не буду давать описание, так как оно в изобилии. Итак, вот моя реализация:

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

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

Это может потребовать настройки (например, изначально обратный вызов не вызывается сразу).

Посмотрите разницу в действии (попробуйте изменить размер окна):

function throttle(callback, delay) {
    var timeoutHandler = null;
    return function () {
        if (timeoutHandler == null) {
            timeoutHandler = setTimeout(function () {
                callback();
                clearInterval(timeoutHandler);
                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));
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;
}
<table>
    <tr>
        <td>default</td>
        <td>throttle</td>
        <td>debounce</td>
    </tr>
    <tr>
        <td id="cellDefault">
            <div></div>
        </td>
        <td id="cellThrottle">
            <div></div>
        </td>
        <td id="cellDebounce">
            <div></div>
        </td>
    </tr>
</table>

Ответ 7

Я сделал пакет npm с некоторыми функциями регулирования:

npm установить функцию-регулятор

throttleAndQueue

Возвращает версию вашей функции, которую можно вызывать максимум каждые W миллисекунд, где W - это ожидание. Вызовы к вашему функционалу, которые происходят чаще, чем W, ставятся в очередь для вызова каждые W мс

throttledUpdate

Возвращает версию вашей функции, которую можно вызывать максимум каждые W миллисекунд, где W - это ожидание. для вызовов, которые происходят чаще, чем W, последним будет вызов (последний имеет приоритет)

дроссель

ограничивает вашу функцию для вызова максимум каждые W миллисекунд, где W - ожидание. Звонки через W сбрасываются

Ответ 8

Для этой цели есть библиотека Backburner.js от Ember.

https://github.com/BackburnerJS/

Вы бы использовали это так.

var backburner = new Backburner(["task"]); //You need a name for your tasks

function saySomething(words) {
  backburner.throttle("task", console.log.bind(console, words)
  }, 1000);
}


function mainTask() {
  "This will be said with a throttle of 1 second per word!".split(' ').map(saySomething);
}

backburner.run(mainTask)

Ответ 9

Вот моя собственная версия поста Vikas:

throttle: function (callback, limit, time) {
    var calledCount = 0;
    var timeout = null;

    return function () {
        if (limit > calledCount) {
            calledCount++;
            callback(); 
        }
        if (!timeout) {
            timeout = setTimeout(function () {
                calledCount = 0
                timeout = null;
            }, time);
        }
    };
}

Я считаю, что использование setInterval не очень хорошая идея.

Ответ 10

Ниже простейший дроссель, о котором я мог думать, в 13 LOC. Он создает тайм-аут каждый раз, когда функция вызывается и отменяет старую. Исходная функция вызывается с соответствующим контекстом и аргументами, как и ожидалось.

function throttle(fn, delay) {
  var timeout = null;

  return function throttledFn() {
    window.clearTimeout(timeout);
    var ctx = this;
    var args = Array.prototype.slice.call(arguments);

    timeout = window.setTimeout(function callThrottledFn() {
      fn.apply(ctx, args);
    }, delay);
  }
}

// try it out!
window.addEventListener('resize', throttle(function() {
  console.log('resize!!');
}, 200));

Ответ 11

Эта функция газа основана на ES6. Функция обратного вызова принимает аргументы (аргументы), и все же она работает, обернутая функцией газа. Будьте свободны, чтобы настроить время задержки в соответствии с потребностями вашего приложения. 1 раз на 100 мс используется для режима разработки, событие "oninput" является лишь примером частого случая его использования:

const callback = (...args) => {
  console.count('callback throttled with arguments:', args);
};

throttle = (callback, limit) => {
  let timeoutHandler = 'null'

  return (...args) => {
    if (timeoutHandler === 'null') {
      timeoutHandler = setTimeout(() => {            
        callback(...args)
        timeoutHandler = 'null'
      }, limit)
    }
  }
}

window.addEventListener('oninput', throttle(callback, 100));

PS Как объяснил @Anshul: регулирование обеспечивает максимальное количество раз, когда функция может быть вызвана с течением времени. Как в "выполнять эту функцию не чаще, чем раз в 100 миллисекунд".

Ответ 12

В приведенном ниже примере попробуйте нажать кнопку несколько раз, но функция myFunc будет выполняться только один раз в 3 секунды. Функция throttle передается с функцией, которая должна быть выполнена, и с задержкой. Она возвращает замыкание, которое хранится в obj.throttleFunc. Теперь, поскольку obj.throttleFunc хранит замыкание, значение isRunning сохраняется внутри него.

function throttle(func, delay) {
  let isRunning;
  return function(...args) {
    let context = this;        // store the context of the object that owns this function
    if(!isRunning) {
      isRunning = true;
      func.apply(context,args) // execute the function with the context of the object that owns it
      setTimeout(function() {
        isRunning = false;
      }, delay);
    }
  }
}

function myFunc(param) {
  console.log('Called ${this.name} at ${param}th second');
}

let obj = {
  name: "THROTTLED FUNCTION ",
  throttleFunc: throttle(myFunc, 3000)
}

function handleClick() {
  obj.throttleFunc(new Date().getSeconds());
}
button {
  width: 100px;
  height: 50px;
  font-size: 20px;
}
    <button onclick="handleClick()">Click me</button>

Ответ 13

Не нужно тонны локальных переменных для приличной функции газа. Целью функции регулирования является сокращение ресурсов браузера, а не применение таких дополнительных затрат, которые вы используете еще больше. В качестве доказательства этого утверждения я разработал дроссельную функцию, которая имеет только 4 "зависающие" переменные. (Переменная "зависания" - это переменная, которая никогда не собирается сборщиком мусора, поскольку на нее всегда ссылается функция, которая потенциально может быть вызвана, тем самым впитывая память.) Горстка функций газа обычно не приносит никакого вреда; но, если есть тысячи дросселированных функций, тогда памяти становится мало, если вы используете действительно неэффективную функцию дросселя. Мое решение ниже.

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

Затем, чтобы обернуть эту функцию регулирования вокруг EventTarget для таких вещей, как щелчки DOM, события окна, XMLHttpRequests onprogress, FileReader onprogress, [и т.д.], например так:

var tfCache = []; // throttled functions cache
function listen(source, eventName, func, _opts){
    var i = 0, Len = tfCache.length, cF = null, options = _opts || {};
    a: {
        for (; i < Len; i += 4)
            if (tfCache[i] === func &&
              tfCache[i+1] === (options.ALTERNATE||null) &&
              tfCache[i+2] === (options.INTERVAL||200)
            ) break a;
        cF = throttle(func, options.ALTERNATE||null, options.INTERVAL||200);
        tfCache.push(func, options.ALTERNATE||null, options.INTERVAL||200, cF);
    }
    source.addEventListener(eventName, cF || tfCache[i+3], _opts);
    return cF === null; // return whether it used the cache or not
};
function mute(source, eventName, func, _opts){
    var options = _opts || {};
    for (var i = 0, Len = tfCache.length; i < Len; i += 4)
        if (tfCache[i] === func &&
          tfCache[i+1] === (options.ALTERNATE||null) &&
          tfCache[i+2] === (options.INTERVAL||200)
        ) {
            source.removeEventListener(eventName, tfCache[i+3], options);
            return true;
        }
    return false;
}

Пример использования:

(function(){"use strict";
// The function throttler //
var timenow = self.performance?performance.now.bind(performance):Date.now;
function throttle(func, alternateFunc, minInterval) {
    var lastTimeWent = -1;
    return function() {
        var newTimeWent = timenow();
        if ((newTimeWent-lastTimeWent) > minInterval) {
            lastTimeWent = newTimeWent;
            return func.apply(this, arguments);
        } else if (typeof alternateFunc === "function")
            return alternateFunc.apply(this, arguments);
    };
}
// The EventTarget wrapper: //
var tfCache = []; // throttled functions cache
function listen(source, eventName, func, _opts){
    var i = 0, Len = tfCache.length, cF = null, options = _opts || {};
    a: {
        for (; i < Len; i += 4)
            if (tfCache[i] === func &&
              tfCache[i+1] === (options.ALTERNATE||null) &&
              tfCache[i+2] === (options.INTERVAL||200)
            ) break a;
        cF = throttle(func, options.ALTERNATE||null, options.INTERVAL||200);
        tfCache.push(func, options.ALTERNATE||null, options.INTERVAL||200, cF);
    }
    source.addEventListener(eventName, cF || tfCache[i+3], _opts);
    return cF === null; // return whether it used the cache or not
};
function mute(source, eventName, func, _opts){
    var options = _opts || {};
    for (var i = 0, Len = tfCache.length; i < Len; i += 4)
        if (tfCache[i] === func &&
          tfCache[i+1] === (options.ALTERNATE||null) &&
          tfCache[i+2] === (options.INTERVAL||200)
        ) {
            source.removeEventListener(eventName, tfCache[i+3], options);
            return true;
        }
    return false;
}
// Finally, the key logger: //
var keysEle = document.getElementById("keyspressed");
var recordKeyStroke = function(dir,color){return function listener(Evt){
    if (Evt.key=="e") { mute(document, 'keydown', listener, downOptions);
                        mute(document, 'keyup', listener, upOptions); }
    if (!Evt.repeat) keysEle.insertAdjacentHTML(
        "afterbegin",
        '<div class="'+(Evt.key=="e"?"red":color)+'">'+dir+Evt.key+'</div>'
    );
}};
var downOptions = {passive:1, ALTERNATE: recordKeyStroke("+","grey") };
listen(document, 'keydown', recordKeyStroke("+","green"), downOptions);
var upOptions = {passive:1, ALTERNATE: recordKeyStroke("-","grey") };
listen(document, 'keyup',   recordKeyStroke("-","green"), upOptions);
})();
The keys you press and release are shown below.
Those in grey are ones which were passed up by the throttle.
Those in green are ones that were recorded by the throttle.
When the "e" key is pressed, it is red and keystrokes are no longer recorded.
"+[key]" = keydown and "-[key]" = keyup.
<div id="keyspressed" style="white-space:pre-wrap;font-family:'Roboto Mono',monospace;"></div>
<style>.red{color:#f77}.green{color:#5f5}.grey{color:#aaa}</style>