JavaScript: Debounce & Throttle
#Code

JavaScript: Debounce & Throttle

Einer der Dinge, die JavaScript ausmachen, ist das Reagieren auf Events. So lässt sich eine bestimmte Funktion ausführen, wenn die Größe des Fensters verändert wird oder wenn gescrollt wird. Es hat aber auch seine Tücken: Je nach Komplexität der auszuführenden Funktion und der Geschwindigkeit des Auftretens der Events können die Auswirkungen auf die Performance verheerend sein - unflüssige Scrollbewegungen, langsame Animationen bis hin zum kompletten Einfrieren des Browsers

Debounce und throttle Funktionen bieten hier Abhilfe. Der Sinn dieser beiden Funktionen ist es mehrere Events zu bündeln und die Menge der Funktionsaufrufe zu reduzieren. Der Unterschied zwischen den Beiden liegt hierbei nur in der Weise, wie verschiedene Events gebündelt und wann die Funktion ausgeführt wird.

Theoretisches

Das hier beschriebene Verhalten kann in der dazu eingerichteten Demoseite visualisiert werden.

Throttle

Eine throttle Funktion bietet die Möglichkeit viele auftretende Events zu "bremsen" und diese regelmäßig auszuführen. Wird eine throttle Funktion mit einem Intervall von 15ms definiert, so wird bei einer "Flut" an Events die definierte Funktion regelmäßig, alle 15ms, ausgeführt.

Debounce

Eine debounce Funktion wartet auf Events und führt die auszuführende Funktion erst aus, wenn nach einer gewissen Zeit kein neues Event ausgelöst wurde. Wird eine debounce Funktion mit einer Zeit von 15ms definiert, so wird so lange die Funktion nicht ausgeführt, bis das Event für 15ms nicht mehr gefeuert wird. Wird auf die Bewegung der Maus gehört und der Nutzer bewegt seine Maus gleichmäßig für 1 Minute, so wird die Funktion während der ganzen Minute nicht ausgeführt.

Implementierung

Hinweis: Die hier gezeigten Funktionen sind keine Eigenentwicklungen, sondern wurden von Remy Sharp übernommen. Die debounce Funktion wurde entsprechend angepasst, um konsistent mit der throttle Funktion zu sein (context als letzten Parameter, anstatt imidiate).

 

var helpers = {
  /**
   * debouncing, executes the function if there was no new event in $wait milliseconds
   * @param func
   * @param wait
   * @param scope
   * @returns {Function}
   */
  debounce: function(func, wait, scope) {
    var timeout;
    return function() {
      var context = scope || this, args = arguments;
      var later = function() {
        timeout = null;
        func.apply(context, args);
      };
      clearTimeout(timeout);
      timeout = setTimeout(later, wait);
    };
  },
  
  /**
   * In case of a "storm of events", this executes once every $threshold
   * @param fn
   * @param threshold
   * @param scope
   * @returns {Function}
   */
  throttle: function(fn, threshold, scope) {
    threshold || (threshold = 250);
    var last, deferTimer;
    
    return function() {
      var context = scope || this;
      var now = +new Date, args = arguments;
      
      if (last && now < last + threshold) {
        // Hold on to it
        clearTimeout(deferTimer);
        deferTimer = setTimeout(function() {
          last = now;
          fn.apply(context, args);
        }, threshold);
      } else {
        last = now;
        fn.apply(context, args);
      }
    };
  }
}

var resizeHandler = function(){
  this.doSomeMagic();
  this.blackMagic();
}

// Debounce by waiting 0.25s (250ms) with "this" context
window.addEventListener('resize', helpers.debounce(resizeHandler, 250, this));

 

Echte vs. gefühlte Performance bei throttle

Es steht außer Frage, dass in vielen Fällen eine throttle Funktion die Performance, besonders auf schwächeren Geräten, erhöhen kann, da weniger Funktionsaufrufe stattfinden. Hier ist jedoch der Einsatzzweck wichtig: sollen durch die Funktion sanfte Animationen realisiert werden, kann durch einen falsch gewählten threshold der Eindruck von schlechter Performance durch niedrige Bildraten entstehen. Für eine flüssig wirkende Animation sollte dabei min. 30fps vorhanden sein. 

 

// Calculate frames per second based on threshold
var threshold = 50;
var fps = 1000 / threshold // (max. 20fps - a lower threshold would make more sense)

// Calculate threshold based on frames per second
var maxfps = 60;
var threshold = 1000 / maxfps;