Des Browsers Trickkiste

JavaScript-Applikationen mit Developer Tools debuggen und profilen
Kommentare

Jeder Webentwickler kennt sie und doch nutzen nur wenige ihre Fähigkeiten. Die Rede ist von den Entwicklertools, die von den Webbrowsern zur Verfügung gestellt werden. Dieser Artikel soll Ihnen diese Sammlung äußerst mächtiger Werkzeuge näherbringen, können sie doch Großes leisten, wenn es um das Auffinden von Fehlern oder Performanceengpässen geht.

Die gute Nachricht zuerst: Solange Sie nicht auf einem Internet Explorer 6 arbeiten müssen, können Sie auf nahezu jedem gängigen Browser auf einen ähnlich umfangreichen Satz von Entwicklerwerkzeugen zurückgreifen. Im Zuge dieses Artikels stelle ich Ihnen stellvertretend die Chrome Developer Tools vor. Die Funktionalität finden Sie allerdings auch in ähnlicher Form in Firefox oder im Internet Explorer. Für die Details zu den jeweiligen Features möchte ich Sie allerdings auf die Dokumentation Ihres Browsers verweisen.

Damit Sie Ihre Entwicklungswerkzeuge benutzen können, müssen Sie diese zunächst öffnen. Was erst einmal offensichtlich klingt, ist vor allem bei der Benutzung des Debuggers manchmal ein Stolperstein. Haben Sie Ihre Entwicklertools nicht geöffnet, reagiert der Debugger auch nicht auf Anweisungen – doch dazu später mehr.

Die Entwicklertools erreichen Sie je nach Browser und System entweder über Menüpunkte des Browsers oder über bestimmte Tastenkombinationen, was sich vor allem bei häufiger Verwendung als sehr praktisch erweist. Bevor Sie mehr über die wirklich wertvollen Funktionen Ihres Browsers erfahren, lernen Sie zunächst einen alten Bekannten näher kennen: die Browserkonsole und das zugehörige console-Objekt.

Oft unterschätzt: das „console“-Objekt

Die Zeiten, als man zum Debuggen einer Applikation noch die confirm– und alert-Funktionen des Browsers benutzt hat, sind schon lange vorbei. Verfügen die Browser doch mittlerweile über ein Objekt mit dem Namen console, das Ihnen den Zugriff auf die Browserkonsole erlaubt.

Die Browserkonsole ist nichts anderes als eine Schnittstelle in die aktuelle Laufzeitumgebung Ihrer Applikation. Hier können Sie jedes gültige JavaScript-Kommando absetzen und so direkt mit Ihrer Applikation interagieren. Sie können beispielsweise Variablen manipulieren oder Funktionen aufrufen. Sie müssen allerdings beachten, dass es sich dabei um die aktuelle Umgebung handelt, Sie können dadurch also die Zeit weder vor- noch zurückspulen. Um die Umgebung zu einem bestimmten Zeitpunkt im Leben Ihrer Applikation zu erhalten, müssen Sie den Debugger verwenden.

Neben der Möglichkeit, JavaScript auf der Konsole zu programmieren, können Sie über das console-Objekt, das Ihnen Ihr Browser als Hostobjekt – also als von der Umgebung abhängiges Objekt – zur Verfügung stellt, auch Ausgaben auf der Konsole erzeugen.

Einfache Ausgaben generieren Sie über die log-Methode des console-Objekts. Diese Methode akzeptiert beliebig viele Argumente, die als Zeichenkette interpretiert werden und auf der Konsole ausgegeben werden. Ein Feature, das hingegen eher weniger Entwickler kennen, ist die Unterstützung von Platzhaltern in console.log. Hierbei arbeitet die Funktion ähnlich wie die printf-Funktion in anderen Programmiersprachen. Das erste Argument ist eine Zeichenkette, die beispielsweise einen mit „%s“ ausgezeichneten Platzhalter enthält. Dieser wird bei der Ausführung des Kommandos durch das zweite Argument, das Sie der Methode übergeben haben, ersetzt.

Wo Sie mit console.log recht schmucklose Ausgaben erzeugen, können Sie mit console.info, console.warn und console.error unterschiedliche Typen von Meldungen erzeugen. Diese unterscheiden sich durch ein schickes, vorangestelltes Icon, das den jeweiligen Typ repräsentiert; und durch die Tatsache, dass console.error zusätzlich einen aktuellen Stacktrace ausgibt.

Bestimmt wollten Sie auch schon einmal die Performance einer Routine messen oder zwei verschiedene Lösungsansätze hinsichtlich der benötigten Zeit miteinander vergleichen. Implementieren Sie diese Funktionalität selbst, müssen Sie zwei Datumsobjekte generieren und deren Timestamps miteinander vergleichen. Sie müssen das Rad jedoch nicht neu erfinden, denn Ihr Browser beinhaltet bereits eine eingebaute Stoppuhr in Form der Methode console.time. Dieser Methode übergeben Sie ein Label und starten damit den Timer. Sobald alle gewünschten Aktionen ausgeführt sind, rufen Sie die Methode console.timeEnd mit dem gleichen Label auf und erhalten als Ausgabe auf der Konsole das Label und die Anzahl der verstrichenen Millisekunden. Der Funktionsumfang des console-Objekts umfasst noch einige weitere Funktionen, von denen Sie die wichtigsten kurz in Tabelle 1 beschrieben finden.

Tabelle 1: Methoden des console-Objekts

Nachdem Sie jetzt wissen, wie Sie die Konsole Ihres Browsers nutzen und auch aus Ihrer Applikation heraus Ausgaben auf ihr erzeugen können, erfahren Sie im nächsten Abschnitt, wie Sie den Debugger Ihres Browsers dazu verwenden können, an einem beliebigen Punkt in Ihrer Applikation anzuhalten und sich dort genau umzusehen.

Ein Blick ins Innere: der Debugger

Die Methode console.log und ihre Freunde haben zwei entscheidende Nachteile. Zum einen müssen Sie Ihren Quellcode verändern, um Informationen zu erhalten. Das kann zum einen immer Schreibfehler nach sich ziehen. Und zum zweiten kann es durchaus vorkommen, dass Sie eine oder mehrere Ausgaben erzeugen und anschließend feststellen müssen, dass die gewünschte Information doch nicht dabei war. Das bedeutet, dass Sie ein zusätzliches console.log-Statement einfügen und den Workflow erneut durchlaufen müssen.

Breakpoints

Mit console.log und seinen Verwandten können Sie Ausgaben auf der Konsole erzeugen. Stellen Sie allerdings fest, dass die Information, die Sie eigentlich haben wollten, nicht dabei ist, müssen Sie ein weiteres Statement einfügen und den gesamten Workflow erneut durchlaufen. Viel besser ist es da doch, wenn Sie einen bestimmten Punkt in Ihrer Applikation definieren, an dem die Ausführung angehalten wird und Sie die Kontrolle übernehmen können. Dieser Punkt trägt den Namen Breakpoint und kann auf zweierlei Arten gesetzt werden. Die erste – unschönere Variante – besteht darin, dass Sie einfach das Schlüsselwort debugger in Ihren Quellcode einfügen. In der zweiten Variante öffnen Sie in Ihrer Browserkonsole den Sources-Tab, wählen in der Baumansicht die gewünschte Datei aus und klicken auf die Zeilennummer, an der Sie anhalten möchten. Diese Zeile wird dann durch einen Marker hervorgehoben (Abb. 1).

Abb. 1: Das Setzen von Breakpoints über den Sources-Tab

Abb. 1: Das Setzen von Breakpoints über den „Sources“-Tab

Egal, welchen der beiden Wege Sie wählen, gilt: Sobald Sie Ihre Applikation neu laden und Ihre Entwicklertools geöffnet sind, hält Ihr Browser an dieser Stelle an. Sobald dies der Fall ist, können Sie zu Ihrer Konsole wechseln und mit dem aktuellen Stand Ihrer Applikation interagieren. Dabei haben Sie nicht nur Zugriff auf globale Variablen zum Zeitpunkt des Anhaltens, sondern auch auf die Variablen des aktuellen Funktions- und des Closure-Scopes. Wissen Sie einmal nicht, welche Variablen Ihnen gerade zur Verfügung stehen, können Sie einfach einen Blick in die Liste der aktuellen Variablen unter dem Punkt „Scope Variables“ auf der rechten unteren Seite des Fensters werfen.

Sie sind jedoch nicht darauf beschränkt, Breakpoints nur in Ihrem JavaScript-Quellcode zu setzen – auch bei Manipulationen am DOM, also der HTML-Struktur Ihrer Applikation, können Sie Ihre Applikation anhalten lassen. Zu diesem Zweck wählen Sie einfach im Elements-Tab ein bestimmtes Element aus, öffnen mit einem Rechtsklick das Kontextmenü und verwenden den Punkt „Break on“, um zu entscheiden, ob Sie anhalten möchten, wenn sich ein untergeordnetes Element ändert, der Wert eines Attributs des Elements geändert oder das Element entfernt wird. Zudem können Sie auch für den Aufruf eines bestimmten URLs oder das Auftreten eines Events, wie es beispielsweise ein Mausklick oder ein Tastendruck ist, einen Breakpoint definieren. Breakpoints auf das allgemeine mousemove-Event zu setzen ist übrigens in der Regel eine eher schlechte Idee, da die Ausführung Ihrer Applikation unterbrochen wird, sobald sich Ihre Maus auch nur einen Millimeter bewegt.

Es gibt Situationen, in denen Sie während des Debuggens Ihrer Applikation eine Routine mehr als nur einmal durchlaufen. Stellen Sie sich dazu eine Schleife mit mehreren hundert Durchläufen vor: Das Problem, das Sie suchen, tritt jedoch erst beim fünfzigsten oder gar hundertsten Durchlauf auf. Sie können jetzt ein if-Statement in Ihren Quellcode einfügen, das genau diese Situation nachstellt und einen Breakpoint darauf setzen oder Sie lassen Ihren Quellcode unangetastet und nutzen die so genannten „bedingten Breakpoints“. Diese Art von Breakpoint wird erst aktiv, wenn eine bestimmte Bedingung, die Sie zuvor formuliert haben, den Wert true ergibt. Die Bedingungen können dabei beliebig komplex werden. Sobald Sie allerdings nicht mehr auf einen Blick verstehen, warum die Ausführung Ihrer Applikation an einem bedingten Breakpoint anhält oder nicht, ist dies ein gutes Zeichen, dass der Ausdruck vielleicht zu komplex ist.

Sind Sie erst einmal auf den Geschmack gekommen und nutzen Breakpoints, kann es sein, dass Sie den Überblick verlieren und einfach zu viele Breakpoints gesetzt haben. Das ist allerdings auch kein Problem, denn Ihr Browser verfügt über eine Liste aller Breakpoints. Neben den Informationen, in welcher Datei und auf welcher Zeile Sie den Breakpoint gesetzt haben, sehen Sie außerdem den Codeausschnitt, auf dem der Breakpoint liegt. Hier haben Sie die Möglichkeit, durch eine Checkbox den Breakpoint zu aktivieren oder zu deaktivieren.

Navigation

Sobald die Ausführung Ihres Skripts an einem Breakpoint anhält, stehen Sie vor dem Problem, dass Sie irgendwann die Ausführung fortsetzen möchten. Zu diesem Zweck können Sie auf eine kleine Navigationsleiste zugreifen, die Ihnen die wichtigsten Kommandos zur Verfügung stellt. Neben dieser Navigationsleiste können Sie jedes Kommando außerdem über eine Tastenkombination auslösen. Das wichtigste Kommando ist Resume. Dieser Befehl setzt die Ausführung des Skripts bis zum nächsten Breakpoint oder zum Ende des Skripts fort. Sie können außerdem über das nächste Kommando springen und sich so zeilenweise durch Ihre Applikation bewegen. Erreichen Sie das Ende einer Funktion, springen Sie aus ihr heraus und setzen Ihren Weg in der Zeile nach dem Funktionsaufruf fort. Befinden Sie sich in einer Zeile mit einem Funktionsaufruf, können Sie mit dem Step-into-Kommando in die Funktion hinein, und mit dem Step-out-Kommando aus der Funktion herausspringen. Diese beiden Aktionen bewirken, dass Sie einen Eintrag zum aktuellen Callstack hinzufügen beziehungsweise den obersten Eintrag entfernen.

Watch Expressions

Wie bereits erwähnt, können Sie auf eine Übersicht über alle im Debugging-Kontext verfügbaren Variablen zurückgreifen. Manchmal kann diese Übersicht aber recht impraktikabel sein, vor allem, wenn Sie im aktuellen Kontext auf sehr viele Variablen zugreifen können oder wenn Sie nur an einer bestimmten Eigenschaft eines Objekts oder an einem bestimmten Array-Element interessiert sind. In diesen Fällen sollten Sie auf „Watch Expressions“ zurückgreifen. In einem eigenen Unterbereich des Sources-Tabs sehen Sie eine Liste Ihrer aktuellen Watch Expressions. Diese werden bei jedem Schritt, den Sie mit dem Debugger machen, aktualisiert. Hier haben Sie selbst die Kontrolle, welche und wie viele Informationen für Sie gerade relevant sind.

Mit dem Debugger haben Sie Zugriff auf ein sehr mächtiges Werkzeug, das es Ihnen erlaubt, Schritt für Schritt durch Ihre Applikation zu gehen und nahezu jeden Fehler zu finden. Die Grenzen des Debuggers liegen jedoch bei zeitkritischen Operationen. Sie haben keine Möglichkeit, Einfluss auf die Zeit des Browsers zu nehmen. Der einzige Weg, den Sie in diesem Fall beschreiten können, ist, in jeder Callback-Funktion, die für Sie relevant ist, einen eigenen Breakpoint zu setzen. Verschärft wird diese Problemstellung, wenn Probleme auftreten, weil eine bestimmte Konstellation zu einem bestimmten Zeitpunkt auftritt. Durch das Anhalten im Debugger wird der Einfluss, den das Rendering des Browsers oder ähnliche performancekritische Faktoren ausüben, entschärft und potenzielle Probleme lassen sich nicht nachstellen. Um derartigen Fehlern auf die Spur zu kommen, müssen Sie auf andere Werkzeuge der Developer Tools zurückgreifen, wie den Profiler oder die Timeline.

Ein weiteres Problemfeld sind Operationen, die von externen Systemen, zum Beispiel von einem Server, abhängen. Auch hier können Sie nicht kontrollieren, wann und wie ein Server antwortet. Es bleibt Ihnen wiederum lediglich übrig, wohl überlegt Ihre Breakpoints zu platzieren, um einem potenziellen Fehler auf die Spur zu kommen.

Wie Sie eben erfahren haben, kann die Kommunikation mit dem Server ein kritischer Faktor für das reibungslose Funktionieren Ihrer Applikation sein. Aus genau diesem Grund bieten Ihnen die Developer Tools die Möglichkeit, die aus- und eingehende Kommunikation sehr genau zu beobachten.

Im zweiten Teil stellen wir weitere Funktionalitäten vor, die dann ins Spiel kommen, wenn man mit den Standardfunktionen an die Grenzen des Machbaren gelangt.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -