Zum Hauptinhalt springen

[Lv2] JavaScript-Rechenleistungsoptimierung: Debounce, Throttle, Time Slicing

Durch Techniken wie Debounce, Throttle, Time Slicing und requestAnimationFrame wird die JavaScript-Rechenleistung optimiert und die Benutzererfahrung verbessert.


Problemhintergrund​

Im Plattformprojekt führen Benutzer häufig folgende Aktionen aus:

  • Suche (Eingabe von Stichworten zur Echtzeitfilterung von 3000+ Produkten)
  • Listen scrollen (Position verfolgen und mehr Inhalte beim Scrollen laden)
  • Kategorien wechseln (Filtern zur Anzeige bestimmter Produkttypen)
  • Animationseffekte (sanftes Scrollen, Geschenkeffekte)

Ohne Optimierung verursachen diese Aktionen Seitenruckeln und hohe CPU-Auslastung.


Strategie 1: Debounce - Sucheingabeoptimierung​

import { useDebounceFn } from '@vueuse/core';

// Debounce-Funktion: bei erneuter Eingabe innerhalb von 500ms wird der Timer zurückgesetzt
const debounceKeyword = useDebounceFn((keyword) => {
searchGameKeyword(gameState.list, keyword.toLowerCase());
}, 500);

watch(
() => searchState.keyword,
(newValue) => {
debounceKeyword(newValue); // Wird erst 500ms nach dem Aufhören der Eingabe ausgeführt
}
);
Vor der Optimierung: Eingabe von "slot game" (9 Zeichen)

- Löst 9 Suchen aus
- Filterung von 3000 Spielen x 9 Mal = 27.000 Operationen
- Dauer: ca. 1,8 Sekunden (Seite ruckelt)

Nach der Optimierung: Eingabe von "slot game"

- Löst 1 Suche aus (nach Ende der Eingabe)
- Filterung von 3000 Spielen x 1 Mal = 3.000 Operationen
- Dauer: ca. 0,2 Sekunden
- Performance-Verbesserung: 90%

Strategie 2: Throttle - Scroll-Event-Optimierung​

Anwendungsszenario: Scroll-Position-Tracking, unendliches Scrollen

import { throttle } from 'lodash';

// Throttle-Funktion: innerhalb von 100ms maximal 1 Ausführung
const handleScroll = throttle(() => {
scrollTop.value = document.documentElement.scrollTop;
}, 100);

window.addEventListener('scroll', handleScroll);
Vor der Optimierung:

- Scroll-Event wird 60 Mal pro Sekunde ausgelöst (60 FPS)
- Jede Auslösung berechnet die Scroll-Position
- Dauer: ca. 600ms (Seite ruckelt)

Nach der Optimierung:

- Scroll-Event wird maximal 1 Mal pro Sekunde ausgelöst (100ms zwischen Ausführungen)
- Dauer: ca. 100ms
- Performance-Verbesserung: 90%

Strategie 3: Time Slicing - Verarbeitung großer Datenmengen​

Anwendungsszenario: Tag-Cloud, Menükombinationen, Filterung von 3000+ Spielen, Rendering von Transaktionsverläufen

// Benutzerdefinierte Time-Slicing-Funktion
function processInBatches(
array: GameList, // 3000 Spiele
batchSize: number, // 200 pro Batch
callback: Function
) {
let index = 0;

function processNextBatch() {
if (index >= array.length) return; // Verarbeitung abgeschlossen

const batch = array.slice(index, index + batchSize); // Slice
callback(batch); // Diesen Batch verarbeiten
index += batchSize;

setTimeout(processNextBatch, 0); // Nächster Batch in die Microtask-Queue
}

processNextBatch();
}

Verwendungsbeispiel:

function searchGameKeyword(games: GameList, keyword: string) {
searchState.gameList.length = 0;

// 3000 Spiele in 15 Batches aufteilen, je 200
processInBatches(games, 200, (batch) => {
const filteredBatch = batch.filter((game) =>
game.game_name.toLowerCase().includes(keyword)
);
searchState.gameList.push(...filteredBatch);
});
}

Strategie 4: requestAnimationFrame - Animationsoptimierung​

Anwendungsszenario: sanftes Scrollen, Canvas-Animationen, Geschenkeffekte

const scrollToTopAnimated = (el: any, speed = 500) => {
const startPosition = el.scrollTop;
const duration = speed;
let startTime = null;

// Easing-Funktion
const easeInOutQuad = (t, b, c, d) => {
t /= d / 2;
if (t < 1) return (c / 2) * t * t + b;
t--;
return (-c / 2) * (t * (t - 2) - 1) + b;
};

const animateScroll = (currentTime) => {
if (startTime === null) startTime = currentTime;
const timeElapsed = currentTime - startTime;
const run = easeInOutQuad(
timeElapsed,
startPosition,
-startPosition,
duration
);
el.scrollTop = run;

if (timeElapsed < duration) {
requestAnimationFrame(animateScroll); // Rekursiver Aufruf
}
};

requestAnimationFrame(animateScroll);
};

Warum requestAnimationFrame verwenden?

// Falscher Ansatz: setInterval verwenden
setInterval(() => {
el.scrollTop += 10;
}, 16); // Versuch 60fps (1000ms / 60 = 16ms)
// Probleme:
// 1. Nicht mit dem Browser-Repaint synchronisiert (kann mehrmals zwischen Repaints ausgeführt werden)
// 2. Wird auch in Hintergrund-Tabs ausgeführt (Ressourcenverschwendung)
// 3. Kann zu Jank führen (Frame-Verlust)

// Richtiger Ansatz: requestAnimationFrame verwenden
requestAnimationFrame(animateScroll);
// Vorteile:
// 1. Mit dem Browser-Repaint synchronisiert (60fps oder 120fps)
// 2. Pausiert automatisch, wenn Tab nicht sichtbar (Energiesparen)
// 3. Flüssiger, kein Frame-Verlust

Interview-Schwerpunkte​

Debounce vs Throttle​

EigenschaftDebounceThrottle
AuslösezeitpunktNach Beendigung der Aktion, wartet eine ZeitspanneMaximal eine Ausführung in festem Zeitintervall
Geeignetes SzenarioSucheingabe, Fenster-ResizeScroll-Events, Mausbewegung
AusführungsanzahlKann nicht ausgeführt werden (bei fortlaufender Auslösung)Garantierte Ausführung (feste Frequenz)
VerzögerungMit Verzögerung (wartet auf Stopp)Sofortige Ausführung, anschließend begrenzt

Time Slicing vs Web Worker​

EigenschaftTime SlicingWeb Worker
AusführungsumgebungHauptthreadHintergrundthread
Geeignetes SzenarioAufgaben mit DOM-ManipulationReine Berechnungsaufgaben
ImplementierungskomplexitätEinfacherKomplexer (Kommunikation erforderlich)
Performance-VerbesserungVerhindert Blockierung des HauptthreadsEchte parallele Berechnung

Häufige Interview-Fragen​

F: Wie wählt man zwischen Debounce und Throttle?

A: Abhängig vom Anwendungsszenario:

  • Debounce: Geeignet für Szenarien "Warten, bis der Benutzer die Aktion abgeschlossen hat" (z.B. Sucheingabe)
  • Throttle: Geeignet für Szenarien "Muss kontinuierlich aktualisieren, aber nicht zu häufig" (z.B. Scroll-Tracking)

F: Wie wählt man zwischen Time Slicing und Web Worker?

A:

  • Time Slicing: DOM-Manipulation erforderlich, einfache Datenverarbeitung
  • Web Worker: Reine Berechnung, große Datenmengen, keine DOM-Manipulation nötig

F: Was sind die Vorteile von requestAnimationFrame?

A:

  1. Mit dem Browser-Repaint synchronisiert (60fps)
  2. Pausiert automatisch, wenn Tab nicht sichtbar (Energiesparen)
  3. Vermeidet Frame-Verlust (Jank)
  4. Performance überlegen gegenüber setInterval/setTimeout