Búsqueda de sitios web

jquery - Limitador simple en JavaScript


Estoy buscando un limitador simple en JavaScript. Sé que librerías como lodash y underscore lo tienen, pero solo para una función sería excesivo incluir alguna de esas librerías.

También estaba comprobando si jQuery tiene una función similar, no pude encontrarla.

He encontrado un acelerador que funciona, y aquí está el código:

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

El problema con esto es: dispara la función una vez más después de que se complete el tiempo de limitación. Así que supongamos que hice una limitación que se dispara cada 10 segundos al presionar una tecla: si presiono una tecla 2 veces, todavía se disparará la segunda pulsación de tecla cuando se completen los 10 segundos. No quiero este comportamiento.

Respuesta #1

Usaría el código fuente de underscore.js o lodash para encontrar una versión bien probada de esta función.

Aquí está la versión ligeramente modificada del código de underscore para eliminar todas las referencias a underscore.js en sí mismo:

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

Tenga en cuenta que este código se puede simplificar si no necesita todas las opciones que soporta underscore.

Por favor, encuentre a continuación una versión muy simple y no configurable de esta función:

function throttle (callback, limit) {
    var waiting = false;                      // Initially, we're not waiting
    return function () {                      // We return a throttled function
        if (!waiting) {                       // If we're not waiting
            callback.apply(this, arguments);  // Execute users function
            waiting = true;                   // Prevent future invocations
            setTimeout(function () {          // After a period of time
                waiting = false;              // And allow future invocations
            }, limit);
        }
    }
}

Edición 1: Se eliminó otra referencia al guion bajo, gracias al comentario de @Zettam

Edición 2: Agregada sugerencia sobre lodash y posible simplificación del código, gracias al comentario de @lolzery @wowzery

Edición 3: Debido a solicitudes populares, agregué una versión muy simple, no configurable de la función, adaptada del comentario de @vsync

Respuesta #2

¿Qué hay de esto?

function throttle(func, timeFrame) {
  var lastTime = 0;
  return function () {
      var now = Date.now();
      if (now - lastTime >= timeFrame) {
          func();
          lastTime = now;
      }
  };
}

Simple.

Puede que le interese echar un vistazo a la fuente.

Respuesta #3

callback: toma la función que debe ser llamada

límite: número de veces que se debe llamar a la función dentro del límite de tiempo

tiempo: período de tiempo para reiniciar el conteo del límite

funcionalidad y uso: Supongamos que tienes una API que permite al usuario llamarla 10 veces en 1 minuto

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

Respuesta #4

Agregando a la discusión aquí (y para los visitantes más recientes), si la razón para no usar el casi de facto throttle de lodash es tener un paquete o bundle de tamaño más pequeño, entonces es posible incluir solo throttle en tu bundle en lugar de toda la biblioteca lodash. Por ejemplo, en ES6, sería algo así:

import throttle from 'lodash/throttle';

Además, hay un paquete de throttle solo de lodash llamado lodash.throttle que se puede usar con un simple import en ES6 o require en ES5.

Respuesta #5

Solo necesitaba una función de control/límite para el evento de redimensionamiento de la ventana, y por curiosidad, también quería saber qué son y cómo funcionan.

He leído múltiples publicaciones de blog y preguntas y respuestas en SO, pero todas parecen complicar demasiado esto, sugieren bibliotecas o simplemente proporcionan descripciones y no implementaciones simples en JS puro.

No proporcionaré una descripción ya que es abundante. Así que aquí está mi implementación:

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

Estos podrían necesitar ajustes (por ejemplo, inicialmente la devolución de llamada no se llama de inmediato).

Vea la diferencia en acción (intente cambiar el tamaño de la ventana):

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

JSFiddle

Respuesta #6

Aquí está cómo implementé la función throttle en ES6 en 9 líneas de código, espero que ayude

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

Haz clic en este enlace para ver cómo funciona.

Artículos relacionados