Debouncing und Throttling: Hochfrequente Events in JavaScript zähmen
Jeder Frontend-Entwickler rennt irgendwann gegn dieselbe Wand: a Scroll-Handler, der hunderte Moi pro Sekunde feuert, a Suchfeld, des bei jedem Tastenanschlag an Netzwerk-Request auslöst, oda a Resize-Listener, der s'Layout so oft neu berechnet, dass die Seitn zum Ruckeln anfangt. Debouncing und Throttling sand de zwoa Techniken, de des beheben, und sobois du den Unterschied kennst, wird die Auswahl automatisch.
Des Problem: Events feuern schneller, ois du sie bearbeitn kannst
Browser drosseln DOM-Events ned für dich. scroll, resize, mousemove und input kinnan olle Dutzende Moi pro Sekunde feuern. Wenn da Handler, der dran hängt, irgendwos Nicht-Triviales mocht - a Fetch, a DOM-Messung, a Re-Render -, machst du die teure Orbeit viel öfter, ois da Nutzer sie eigentlich braucht.
window.addEventListener('resize', () => {
recalculateLayout(); // runs on every single pixel of resize
});
Debouncing und Throttling senken beide, wie oft da Handler tatsächlich lauft - nur mit unterschiedlichen Garantien.
Debounce: Auf die Stille wartn
Debouncing verzögert die Ausführung, bis die Events für a bestimmte Zeit aufhörn. Jeder neiche Aufruf setzt den Timer zruck. Des is des richtige Werkzeug, wennst nur den Endzustand nach ana Aktivitätsserie willst - den Wert in am Suchfeld, sobois da Nutzer aufhört zum Tippen, ned jeden Zwischenanschlag.
function debounce(fn, delayMs) {
let timeoutId;
return function debounced(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn.apply(this, args), delayMs);
};
}
Verwendung: a Suchfeld mit Live-Suche, des ned bei jedem Anschlag s'Netzwerk bemühn soi.
const searchInput = document.querySelector('#search');
const runSearch = debounce((event) => {
fetchResults(event.target.value);
}, 300);
searchInput.addEventListener('input', runSearch);
Tipp "angular" und fetchResults feuert einmal, 300ms nach'm letzten Anschlag - ned siebenmal.
Throttle: A gleichmäßigen Rhythmus garantiern
Throttling begrenzt die Ausführung auf höchstens einmal pro Intervall, egal wia vül Events in dem Fenster einkemman. Des is des richtige Werkzeug für kontinuierliche Ströme, wo du durchgängig periodische Updates brauchst, ned nur am End - a Scroll-Positions-Tracker, a Drag-Handler, a mousemove-gesteuerter Cursor-Effekt.
function throttle(fn, intervalMs) {
let lastCall = 0;
let timeoutId = null;
return function throttled(...args) {
const now = Date.now();
const remaining = intervalMs - (now - lastCall);
if (remaining <= 0) {
clearTimeout(timeoutId);
timeoutId = null;
lastCall = now;
fn.apply(this, args);
} else if (!timeoutId) {
timeoutId = setTimeout(() => {
lastCall = Date.now();
timeoutId = null;
fn.apply(this, args);
}, remaining);
}
};
}
Verwendung: Layout bei Resize neu berechnen, höchstens olle 200ms.
window.addEventListener(
'resize',
throttle(() => {
recalculateLayout();
}, 200),
);
Beachte den else if (!timeoutId)-Zweig: Er plant an nachlaufenden Aufruf ein, damit s'letzte Resize-Event in ana Serie trotzdem bearbeitet wird, statt still verworfen zu wern, weil's ins Cooldown-Fenster gfoin is.
Zwischen den beiden wählen
- Debounce, wennst nur den Endzustand willst: Sucheingabe, Formularvalidierung, Auto-Save, Resize-Handler, de nur einmal laufn soin, wenn si olles beruhigt hot.
- Throttle, wennst während der Aktivität kontinuierliches Feedback brauchst: Infinite-Scroll-Trigger, Scroll-Fortschrittsbalken, Drag-and-Drop, Live-Cursor-Tracking.
Faustregel: Debounce beantwortet "wos hom's am End gmacht?"; Throttle beantwortet "wos machen's grod jetzt, regelmäßig abgetastet?"
Häufige Fallstricke
thisverliern. Beide Implementierungen oben nutzenfn.apply(this, args), drum funktionieren sie korrekt ois Objektmethoden oda normal gebundene Event-Handler. Wennst a Arrow-Funktion einwickelst, de scho s'falschethiseingfangen hot, hüft koa.apply()der Welt - der Fehler passiert früher.- Beim Unmount ned aufräumen. In ana komponentenbasierten Umgebung (React, Angular, Vue) kann a debounceter/gethrottleter Handler no in am
setTimeoutanstehn, nachdem die Komponente, die ihn erzeugt hot, weg is, und wenn er dann feuert, kann er abstürzen oda in veralteten State schreiben. Brich ihn ab: Behalt a Referenz auftimeoutIdund lösch sie im Cleanup-Pfad (ngOnDestroy, Cleanup-Funktion vonuseEffect, etc.). - Debounce-Delay zu lang gsetzt. 300ms is a gängiger Standard für Sucheingaben, aber für so wos wia Inline-Formularvalidierung kann sich a 300ms zäh anfühln. Stimm den Delay auf die Interaktion ab, ned auf a Zoi, die du aus am Blogpost kopiert host.
- Throttle ohne nachlaufenden Aufruf. A naiver Throttle, der nur prüft "is gnua Zeit vergangen?" und sonst nix tut, verwirft still des letzte Event ana Serie. Wenn der letzte Zustand zählt (und des tut er meistens), stell sicher, dass deine Implementierung an nachlaufenden Aufruf einplant wia die oben.
Soit ma's söba schreibn?
Die Implementierungen do sand absichtlich minimal, um den eigentlichen Mechanismus zu zeigen - Closures, de zwischen Aufrufen den Timer-Zustand hoitn. Im Produktivcode greif lieber zu am gewarteten Utility: debounce und throttle von Lodash behandeln Randfälle wia Leading-/Trailing-Edge-Konfiguration und .cancel()/.flush()-Methoden, de von Hand leicht subtil falsch wern. Söba eine zu schreibn is a gute Übung, um zu vastehn, wos unter da Haubn passiert - zu am bewährten zu greifn, is die richtige Entscheidung, sobois's drauf ankummt.
Fazit
Debounce fasst a Serie von Events nach dem Abklingen zu oam Aufruf zamm. Throttle verteilt Aufrufe gleichmäßig, während die Serie läuft. Beide gibt's aus demselben Grund: Da Browser woaß ned, wia schnö dein Code sich leisten kann zum laufn, drum liegt's an dir, es ihm zu sogn.