JavaScript Engines: Performance-Steigerung der Browser

Seite 2: Historische Entwicklung und grundlegende Designentscheidungen

Inhaltsverzeichnis

JavaScript war niemals für die Entwicklung großer Anwendungen konzipiert. Als es 1995 unter der Regie von Brendan Eich im Netscape Navigator 2.0 das erste Mal das Licht der Welt erblickte, gab es dafür bereits einen Platzhirschen: Java. Denn um Java gab es damals einen riesigen Hype: Es galt als C++-Nachfolger und somit als neue "Lingua Franca".

Netscape sah jedoch den Bedarf nach einer "leichtgewichtigen" Alternative. Anwendungsfälle waren insbesondere Formularvalidierungen oder kleine Animationen. Unter keinen Umständen wollte Netscape Java als Konkurrenten herausfordern, da sie zur selben Zeit eine Allianz mit Sun eingingen, also dem damaligen Eigentümer von Java, um sich gemeinsam gegen die Übermacht von Microsoft behaupten zu können.

Deshalb war die neue Programmiersprache als kleiner Bruder von Java konzipiert. Man gab ihr den trendigen Namen JavaScript und hatte somit ein ähnliches Duo im Web wie Microsoft mit Visual Basic und Visual C++ am Desktop. Diese Entscheidungen spiegelten sich im Design von JavaScript wider. Will man nur kleine Anwendungen entwickeln, ist es wichtig, dass das schnell und einfach vonstatten geht. Performance ist für kleine Skripte eher zweitrangig. Deshalb wurden damals die Weichen gestellt, die sich später als Stolperstein bezüglich der Performance herausstellen sollten.

Zwei wesentlichen Faktoren in JavaScript machen den Einstieg relativ simpel: Der Verzicht auf einen Compiler zugunsten eines Interpreters und die dynamische Typisierung. Kurz zusammengefasst führt der Interpreter zur Laufzeit den Quellcode direkt aus. Das heißt, man erspart sich einen Compiler sowie das Setup der benötigten Tools. Man kann somit eine Anwendung direkt im Quelltext ausliefern, was eine ungemeine Vereinfachung ist.

Die dynamische Typisierung ermittelt erst zur Laufzeit den Typ. Das Weglassen der Typinformationen im Quellcode führt zu kürzerem Code und Entwickler können beispielsweise mehrere Typen String oder Number für dieselbe Variable beziehungsweise Funktionsparameter verwenden. Das führt jedoch zur Leistungsminderung: Dynamische Typisierung und Interpreter haben nicht nur Vorteile.

Ein Compiler generiert Maschinencode, den man direkt und ohne Interpreter ausführen kann. Die Ausführungsgeschwindigkeit von Maschinencode ist deutlich schneller als die eines Interpreters. Dazu kommt noch, dass der Compiler den Quellcode analysieren und optimieren kann (unter anderem Inline Caches). Mit dynamischen Typen verhält es sich ähnlich. Zur Laufzeit muss ein Interpreter immer wieder überprüfen, um welchen Typ es sich eigentlich handelt, der beispielsweise einer Variablen zugewiesen ist. Da sich der Typ zur Laufzeit ändern kann, muss die Überprüfung wiederholt stattfinden – das kostet natürlich wertvolle Zeit. Bei statischer Typisierung fällt der Schritt weg, da bekanntlich die Typen feststehen und sich nicht ändern können.

Compiler und statische Typisierung kombiniert bieten noch einen weiteren Vorteil. Bestimmte Fehlerkategorien wie falsche Syntax oder Verwendung von nicht existierenden Properties oder Funktionen (zum Beispiel durch Vertippen) führen zu einem Abbruch des Compiler-Vorgangs. Da das jedoch beim Entwickler vor Ort stattfindet, gelangt derartiger Code nie zum Benutzer.