Reduzieren Sie die JavaScript-Ausführungszeit, indem Sie teure Funktionen identifizieren, unnötige Arbeit entfernen und Funktionen erst bei Bedarf laden. Belegt notwendige Arbeit den Hauptthread weiterhin zu lange, teilen Sie sie in getrennte Aufgaben auf oder verlagern geeignete Berechnungen in einen Web Worker. Downloadgröße, gesamte CPU-Zeit und längste ununterbrochene Aufgabe sind unterschiedliche Probleme: Messen Sie alle drei, bevor Sie eingreifen.
Zeichnen Sie die langsame Interaktion vor der Änderung auf
Verwenden Sie einen Produktionsbuild und eine wiederholbare Aktion, etwa das Öffnen der Navigation, einen Produktfilter oder einen Bericht. Zeichnen Sie Laden und Interaktion getrennt im Performance-Panel der Chrome DevTools auf. Halten Sie Gerät, CPU-Drosselung, Netzwerk und Einwilligungszustand konstant. Erweiterungen und Entwicklungsinstrumentierung können Ergebnisse verändern; nutzen Sie ein sauberes Profil und dokumentieren Sie die Umgebung.
Markieren Sie die Zeitspanne, in der die Oberfläche nicht reagiert. Untersuchen Sie Main und anschließend Bottom-up für Funktionen mit hoher Eigenzeit sowie Call tree für deren Aufrufer. Eigenzeit gehört zur Funktion selbst, Gesamtzeit schließt Unteraufrufe ein. Ein kleiner Wrapper mit hoher Gesamtzeit führt möglicherweise zur Ursache, muss aber nicht selbst umgeschrieben werden. Source Maps verbinden den gebündelten Code mit dem gepflegten Modul.
Laut web.dev gilt eine Aufgabe über 50 Millisekunden als lang. Diese Grenze kennzeichnet blockierende Arbeit, ist aber kein Zeitbudget, das jede Aufgabe ausschöpfen sollte. Auch mehrere kürzere Aufgaben können die Bedienung beeinträchtigen, insbesondere mit konkurrierendem Rendering und Fremdskripten. Untersuchen Sie die Interaktion, statt lediglich Warnmarkierungen zu zählen.
JavaScript-Diagnosen und Treemap von Lighthouse zeigen teure Ressourcen und große Abhängigkeiten. Sie beweisen nicht, dass die größte Datei die Pause verursacht. Vergleichen Sie Dateigröße und Ausführung. Der Leitfaden zur Verbesserung von INP hilft, wenn die Verzögerung Eingabewartezeit, Verarbeitung und nächsten Paint umfasst.
Unterscheiden Sie Entfernen, Verschieben und Scheduling
Wählen Sie den Eingriff anhand der Messung. Komprimierung reduziert übertragene Bytes, doch der Browser verarbeitet das entpackte Programm. Minifizierung ist sinnvoll, ersetzt aber keine Reduktion der tatsächlich ausgeführten Arbeit.
Diese Maßnahmen lösen verschiedene Probleme und müssen getrennt bewertet werden.
| Befund | Zuerst untersuchen | Was nicht garantiert wird |
|---|---|---|
| Unbenötigter Funktionscode auf der Einstiegsroute | Entfernen oder eigener Chunk | Schnellere erstmalige Nutzung |
| Doppelte Initialisierung oder Listener | Wiederholte Arbeit beseitigen | Kleinere Downloads |
| Lange DOM-unabhängige Berechnung | Worker oder besserer Algorithmus | Weniger gesamte CPU-Zeit |
| Notwendige Abfolge blockiert Rückmeldung | Zwischen begrenzten Schritten abgeben | Schnellere Gesamtabwicklung |
| JavaScript löst teure Stil- oder Layoutarbeit aus | DOM-Änderungen und Layoutabhängigkeiten reduzieren | Lösung allein durch Skriptattribute |
Formulieren Sie vorab eine Hypothese: Welche Funktion soll seltener laufen, welcher Chunk beim Start fehlen oder wo eine Rendering-Gelegenheit entstehen? Die nächste Aufzeichnung erklärt dann den Eingriff, statt nur eine weitere Punktzahl zu liefern.
Liefern Sie nur den Code für die aktuelle Ansicht
Prüfen Sie den Produktionsbuild im Bundler-Analyzer oder der Lighthouse-Treemap. Suchen Sie doppelte Paketversionen, ungenutzte Sprachdaten, große Bibliotheken für kleine Hilfsfunktionen und überall geladene optionale Funktionen. Klären Sie zuerst, warum eine Abhängigkeit eingebunden ist. Ein kleinerer Ersatz kann Wartungsaufwand erhöhen oder wichtiges Verhalten verändern.
Tree Shaking entfernt statisch erkennbare ungenutzte Exporte, sofern das Build-System dies unterstützt. ES-Module und korrekte Angaben zu Seiteneffekten helfen. Deklarieren Sie nicht pauschal alle Pakete als seiteneffektfrei: Polyfills, Komponentenregistrierung und wichtige CSS-Importe können erforderlich sein, auch ohne verwendete Exporte. Entscheidend ist das Produktionsergebnis, nicht allein die Importanweisung.
Coverage liefert weitere Hinweise, aber ungenutzter Code einer Aufzeichnung kann nach Anmeldung, auf einer anderen Route oder im Dialog gebraucht werden. Prüfen Sie diese Wege vor dem Löschen. Fehlerbehandlung und Barrierefreiheit gehören dazu; seltene Nutzung macht eine Funktion nicht überflüssig. Entfernen Sie lieber aufgegebene Funktionen, statt benötigten Code fragil umzubauen.
Laden Sie optionale Funktionen per dynamischem Import
Ein dynamischer Import schafft eine Ladegrenze, wenn der Build Code-Splitting unterstützt. Wählen Sie eine unabhängig angeforderte Funktion, etwa eine Exportvorschau. Jede kleine Funktion aufzuteilen verursacht Koordinations- und Anfrageaufwand; eine ganze Route kann für eine selten verwendete Teilfunktion zu grob sein.
Dieses Beispiel verwendet drei über HTTP bereitgestellte Dateien und lädt ein Modul nach Aktivierung. Beschriftung, Fortschritt und Fehlermeldungen muss die Anwendung lokalisieren.
<!-- index.html -->
<button id="preview" type="button">Preview export</button>
<p id="status" role="status"></p>
<pre id="output"></pre>
<script type="module" src="/main.js"></script>Das Einstiegsmodul aktiviert die Schaltfläche auch bei einem Importfehler wieder.
// main.js
const button = document.querySelector('#preview');
const status = document.querySelector('#status');
const output = document.querySelector('#output');
button.addEventListener('click', async () => {
button.disabled = true;
status.textContent = 'Loading preview';
try {
const { renderPreview } = await import('./preview.js');
renderPreview(output);
status.textContent = 'Preview ready';
} catch {
status.textContent = 'Preview unavailable. Try again.';
} finally {
button.disabled = false;
}
});Das separat geladene Modul stellt die optionale Funktion bereit.
// preview.js
export function renderPreview(element) {
element.textContent = JSON.stringify({ format: 'csv', ready: true }, null, 2);
}Das Beispiel demonstriert den Ladevorgang, keine teure Verarbeitung und keine gemessene Optimierung. Reale Module können beim Import erheblich initialisieren. Prüfen Sie die erste Aktivierung mit kaltem Cache und spätere Aufrufe. Wird der erste Aufruf langsam, verbessern Sie die Funktion oder laden vorsichtig bei erkennbarem Nutzerinteresse vor. Verschieben Sie das Startproblem nicht einfach auf den ersten Klick.
Geben Sie dem Browser echte Reaktionsmöglichkeiten
Mehrere synchron aufgerufene kleine Funktionen bleiben eine ununterbrochene Aufgabe. Auch das Warten auf ein bereits erfülltes Promise schafft nicht zuverlässig Zeit zum Rendern: Promise-Fortsetzungen sind Microtasks. Verwenden Sie einen Mechanismus, der in einer späteren Aufgabe fortsetzt, sofern die Operation sicher pausieren kann.
Dieser Helfer bevorzugt scheduler.yield() und verwendet andernfalls einen Timer; die Schleife begrenzt jeden Verarbeitungsschritt.
function yieldToBrowser() {
if (globalThis.scheduler?.yield) return globalThis.scheduler.yield();
return new Promise(resolve => setTimeout(resolve, 0));
}
export async function normalizeRows(rows, batchSize = 100) {
if (!Number.isInteger(batchSize) || batchSize < 1) {
throw new RangeError('batchSize must be a positive integer');
}
const normalized = [];
for (let start = 0; start < rows.length; start += batchSize) {
const end = Math.min(start + batchSize, rows.length);
for (let index = start; index < end; index++) {
normalized.push(String(rows[index]).trim());
}
if (end < rows.length) await yieldToBrowser();
}
return normalized;
}Die Batchgröße ist ein Beispielwert der Anwendung, keine universelle Sicherheitsgrenze. Bereits ein teures Element kann eine lange Aufgabe erzeugen. Stimmen Sie die Größe auf Arbeit und Geräte ab und berücksichtigen Sie Abbruch, wenn Nutzende Eingaben währenddessen ändern. Die Timer-Alternative hat nicht dieselbe Prioritätsbehandlung wie die native API. Prüfen Sie beide Pfade; die offizielle Anleitung zu langen Aufgaben erklärt das Scheduling.
Yielding kann die Reaktionsfähigkeit verbessern, ohne die Gesamtzeit zu senken, und zusätzlichen Aufwand verursachen. Bewahren Sie notwendige atomare Operationen. Melden Sie einen Speichervorgang erst nach seinem Abschluss als erfolgreich. Rückmeldungen wie Verarbeitung läuft müssen den tatsächlichen Zustand beschreiben.
Verlegen Sie unabhängige Berechnungen in einen Worker
Worker führen JavaScript außerhalb des Hauptthreads aus, können das Fenster-DOM aber nicht direkt verändern. Sie eignen sich für unabhängiges Parsen, Datentransformationen und Berechnungen. Bei DOM-Einfügungen, Layoutmessungen oder Paint helfen sie nicht automatisch. Für winzige Operationen können Start und Nachrichten mehr kosten als die lokale Ausführung.
Dieser Modul-Worker demonstriert die Grenze; die Beispieldaten sind bewusst zu klein, um einen Leistungsgewinn zu behaupten.
// totals.worker.js
self.addEventListener('message', event => {
const total = event.data.reduce((sum, value) => sum + value, 0);
self.postMessage(total);
});Der Aufrufer verarbeitet das Ergebnis im Hauptthread und beendet anschließend den Worker.
// totals.js, loaded as a module
const worker = new Worker(new URL('./totals.worker.js', import.meta.url), {
type: 'module'
});
worker.addEventListener('message', event => {
console.log('Total:', event.data);
worker.terminate();
});
worker.addEventListener('error', () => worker.terminate());
worker.postMessage([2, 4, 6]);Definieren Sie im echten Ablauf Eingabevalidierung, Fehlerbehandlung und Abbruch. Wiederkehrende Jobs können einen wiederverwendeten Worker rechtfertigen. Große Nachrichten können Kopien erfordern. Übertragbare Puffer sparen manche Kopie, übertragen jedoch Eigentümerschaft und ändern die weitere Nutzung beim Sender. Messen Sie Gesamtdauer, Verfügbarkeit des Hauptthreads und Speicherverbrauch.
Verschieben Sie Startarbeit ohne spätere Engpässe zu verstecken
Auch ein zurückgestelltes Skript läuft im Hauptthread. async verändert Lade- und Ausführungszeitpunkt; defer bewahrt bei klassischen externen Skripten zusätzlich die Reihenfolge nach dem HTML-Parsing. Keines zerlegt eine teure Funktion. Prüfen Sie Ladeabhängigkeiten im Leitfaden zu renderblockierenden Ressourcen und danach die tatsächliche Ausführung.
Reservieren Sie Leerlauf-Scheduling für optionale Arbeit. requestIdleCallback() ist nicht überall verfügbar und kann verzögert aufgerufen werden. Erkennen Sie Unterstützung, bieten Sie eine Alternative und prüfen Sie einen Timeout. Navigation, Formularvalidierung und notwendige Rückmeldung dürfen nicht auf Leerlauf angewiesen sein. Ein Timeout macht einen großen Callback nicht günstig; begrenzte Arbeit bleibt erforderlich.
Serverseitiges HTML kann sichtbar sein, bevor das Framework Interaktivität anschließt. Hydration kann viel CPU beanspruchen; frühe Sichtbarkeit beweist keine schnelle Bedienung. Begrenzen Sie Client-Komponenten, vermeiden Sie doppelte Initialisierung und nutzen Sie unterstützte selektive oder verzögerte Hydration nur bei korrektem Verhalten. Prüfen Sie Startklicks, Tastatur, Fokus und erstmalige Nutzung jeder Region.
Fremdcode konkurriert um dieselbe Ausführungszeit. Prüfen Sie die Kosten von Drittanbieterskripten und entfernen Sie doppelte Tags vor kompliziertem Scheduling. Dokumentieren Sie Zweck und Aktivierungsbedingung, damit spätere Ergänzungen den Engpass nicht wiederherstellen.
Untersuchen Sie Wiederholungen und unnötiges Rendering
Nicht nur teure Algorithmen kosten JavaScript-Zeit. Günstige Operationen können bei irrelevanten Zustandsänderungen, jedem Scrollereignis oder erneutem Mounten wiederholt laufen. Zählen Sie Aufrufe und ordnen Sie sie der Nutzeraktion zu. Eine falsche Subscription oder ein doppelter Listener lässt sich oft nachhaltiger korrigieren als sein Callback optimieren.
Trennen Sie in interaktiven Listen Datenverarbeitung und Anzeige. Berechnen Sie nur bei geänderten Eingaben neu und bauen Sie nicht die ganze Liste für eine kleine Änderung auf. Memoisierung lohnt sich nur, wenn eingesparte Arbeit Speicher- und Invalidierungsaufwand überwiegt. Falsche Abhängigkeiten liefern veraltete Ergebnisse; ergänzen Sie Korrektheitsprüfungen.
Suchen Sie abwechselnde Stiländerungen und Geometrieabfragen. Letztere können Layoutberechnungen erzwingen, bevor JavaScript fortsetzt. Sammeln Sie, soweit möglich, Messungen vor dem gemeinsamen Schreiben. Eine schnelle Funktion kann andernorts im Trace trotzdem wiederholt teure Stil- und Layoutarbeit auslösen.
Entfernen Sie nicht mehr benötigte Intervalle, Observer und Ereignisabonnements. Eine Aufzeichnung direkt nach dem Laden übersieht Arbeit, die bei Navigation anwächst. Wiederholen Sie den Weg ohne Neuladen und prüfen Sie zunehmende Verarbeitung. Das ist ein Diagnoseszenario, keine pauschale Behauptung über Speicherlecks eines Frameworks.
Prüfen Sie denselben Nutzerweg erneut
Wiederholen Sie die Aufzeichnung unter gleichen Bedingungen sowie mit kaltem Funktionscache, bei einem Wiederholungsbesuch und auf einem langsameren Gerät. Vergleichen Sie Aufgabenlängen und sichtbare Reaktion, nicht nur Bytes. Ein schnellerer Start kann den ersten Klick verschlechtern; ein kürzerer Callback kann weiterhin teures Rendering anstoßen.
Halten Sie Änderung, Nachweise und Rücknahme im Performance-Check fest. Nutzen Sie die Sammlung zur Ladezeitoptimierung für weitere Arbeit und wählen Sie die nächste teure Funktion aus der neuen Aufzeichnung. Behalten Sie Änderungen bei, wenn der ursprüngliche Ablauf besser wird und Lade-, Fehler- und Zugänglichkeitspfade korrekt bleiben.