Geheimnisse der JavaScript-Syntax

Grammatik bei Websprachen

Die Websprachen HTML, CSS und sämtliche rund um das Web verwendete Programmiersprachen sind in ihrem Aufbau bis ins Kleinste definiert und geregelt. Leider kommt man außerhalb eines Informatik- oder Linguistik-Studium nicht mit mit sogenannten Formalen Sprachen in Berührung. Dabei erscheint mir ein gewisses Grundverständnis äußerst hilfreich.

Wer HTML und CSS verstehen und anwenden will, kommt nicht darum herum, die Syntax-Bestandteile und Verkettungsegeln zu kennen. In HTML haben wir bekanntlich Elemente und deren Tags, die wiederum Attribute enthalten. Und dazwischen haben wir wiederum andere Elemente oder einfachen Text. Das sind die einfachen Grundlagen der Textauszeichnung.

Die grundlegende Syntax von CSS ist nicht nennenswert komplizierter, aber ihre Benennungen meist unbekannt. Ein Stylesheet besteht aus sogenannten Regeln. Die bestehen aus einem Selektor und dem Deklarationsblock. Letzter enthält, wie der Name schon sagt, Deklarationen, die aus einer Eigenschaft und einem zugewiesenen Wert bestehen. Dieses Aufbauprinzip verdeutlicht gleichzeitig, wie CSS funktioniert.

JavaScript-Syntax – wer versteht die schon?!

Bei JavaScript ist es nicht mehr so einfach, schließlich geht es dabei weder um Textauszeichnung mit Elementen noch um Formatierungsregeln für entsprechend strukturierte Dokumente. JavaScript ist eine vollwertige Programmiersprache und ein Computerprogramm ist um einiges komplexer.

Viele Fehler, die JavaScript-Anfänger machen, scheinen mir auf das mangelnde Verständnis der basalen Syntax zurückzugehen. Aber auch Profis müssen die Syntaxregeln kennen, um JavaScript wirklich ausschöpfen zu können und fortgeschrittene, elegante Programmiertechniken eigenständig anwenden zu können. Daher möchte ich eine Übersicht über die Grundstrukturen eines JavaScript-Programmes geben. Ich werde mich dabei am ECMAScript-Standard orientieren und die englischen Originalbezeichnungen übernehmen. Es geht mir nicht um die korrekte und vollständige Wiedergabe der formalen Grammatik von ECMAScript, daher werde ich an manchen Stellen Kürzungen und Vereinfachungen vornehmen.

Statements (Anweisungen)

Ein JavaScript-Programm besteht aus einer Aneinandereihung von Statements. Sie strukturieren ein Script grob oder stellen die einzelnen Anweisungen dar, die beim Ausführen des Programmes abgearbeitet werden.

Die Statements, die die Struktur stark bestimmen, sind als Kontrollstrukturen bekannt: if– und switch-Anweisungen, while– und for-Schleifen und so weiter.

Daneben stehen als wichtiger Strukturbildner die Funktionsdeklarationen. (Diese laufen in der ECMAScript-Spezifikation als eigene Kategorie, aber ich zähle sie an dieser Stelle der Einfachheit halber zu den Statements.)

function meineFunktion (...) {
   ...
}

Die restlichen Statements sind im Groben diejenigen Anweisungen, die mit einem Semikolon abgeschlossen werden und nach denen man in der Regel einen Zeilenumbruch einfügt. Sie stecken meist in den oben genannten Kontrollstrukturen und Funktionen. Dazu gehören continue, break und return sowie die Variablen-Deklaration mit var bla = "wert".

Das wohl häufigste Statement ist jedoch das sogenannte Expression Statement. Das ist ein Statement, das aus einer beliebigen Expression besteht. Dies führt uns zu der Frage: Was sind eigentlich Expressions?

Expressions (Ausdrücke)

Expressions kommen fast überall in JavaScript vor und bilden das große Gegenstück zu Statements. Expressions sind quasi mathematische Termini, die nach gewissen Regeln aufgelöst werden, sodass am Ende ein Ergebniswert herauskommt.

Statements (die nicht weitere Statements enthalten können) enthalten für gewöhnlich Expressions. Der Witz dabei ist, dass in der Regel nicht festgelegt ist, wie diese Expressions zusammengesetzt sein müssen – es wird meist nur ein bestimmtes Ergebnis mit einem bestimmten Typ erwartet. Das lässt Programmierern viel Freiheit und ermöglicht besondere und elegante Umsetzungen.

Nehmen wir eine ganz einfache Expression: 1 + 1. Wir haben hier Werte, die mit einem Operator verknüpft werden. Dies ist das Grundmodell für alle Expressions, so kompliziert sie durch dutzende Operanden und Operatoren auch sein mögen.

Die wichtigsten Bestandteile von Expressions sind Identifier (Bezeichner) und Literale. Identifier sind Namen für Variablen und Objekte. Mit Literalen notiert man unter anderem Strings (z.B. "Hallo Welt!"), Zahlen (z.B. 56.12) oder Arrays in Kurzschreibweise (z.B. [1, 2, 3]). Die beiden Einsen im obigen Beispiel sind übrigens Zahlen-Literale.

Operatoren

Zu den genannten Bestandteilen von Expressions kommt ein Arsenal von unterschiedlichen Operatoren hinzu. Aus der Mathematik und der Logik sind die Rechenoperatoren (z.B. +) und die Vergleichsoperatoren (z.B. ==) am bekanntesten. Daneben gibt es nicht minder wichtige Operatoren, die aber selten als solche wahrgenommen werden: Den . zum Zugriff auf Eigenschaften eines Objektes (z.B. objekt.eigenschaft) sowie ( ) zum Aufrufen einer Funktion (z.B. funktion()). Das Zeichen = steht für den Zuweisungoperator. Aber auch new zum Instantiieren eines Objektes und typeof zur Abfrage eines Variablentyps sind Operatoren.

Operatoren benötigen einen Operanden, wie der Negationsoperator !, oder zwei Operanden, wie der Vergleichsoperator <=, oder auch drei, wie der Konditional-Operator x ? y : z.

Viele Operatoren benötigen Operanden von einem bestimmten Typ. Der Negationsoperator ! erwartet zum Beispiel einen Operanden vom Typ Boolean (d.h. true oder false). Die Spezialität von JavaScript liegt darin, dass die Operandentypen bei der Berechnung automatisch umgewandelt werden, sodass sie den Ansprüchen der mit ihnen verknüpften Operatoren genügen – dies nennt sich dynamische Typisierung. Dadurch kann man beispielsweise !123 schreiben, ohne dass etwas Schlimmes passiert. Die Zahl 123 wird einfach in den Typ Boolean umgewandelt, dabei kommt true heraus, um anschließend den Operator anzuwenden.

Die Berechnung des Ergebnisses einer Expression erfolgt nach der Operatorenrangfolge, die bestimmt, welche Teil-Expressions zuerst berechnet werden müssen. Das Prinzip kennen wir aus der Schulalgebra, eine Grundregel lautet etwa »Punkt- vor Strichrechnung«. Nur wird in JavaScript selbst das Aufrufen von Funktionen und das Ansprechen von Objekteigenschaften mit Operatoren gelöst, daher ist die Rangfolge entsprechend lang.

Function Expressions (Funktionsausdrücke)

Wir haben bereits die Funktionsdeklaration (Function Declaration) als Statement kennengelernt. Eine Alternative zum Erzeugen von Funktionen stellen die Functions Expressions dar. Dies ist eine Spezialität von JavaScript, die viele elegante, aber auch unverständliche Schreibweisen erlaubt. Dummerweise ist ihre Syntax mit der der Deklaration fast identisch. Der einzige Unterschied ist, dass die Function Expression eben überall vorkommen können, wo Expressions erlaubt sind. Und Function Expressions müssen keinen Namen haben. Daher nennt man sie auch namenlose oder anonyme Funktionen.

function funktionsname () {
	window.alert("Dies ist eine Function Declaration.");
}
alert( function () { alert("Dies ist eine Function Expression!"); } );

Folgende Statements sind in jedem Fall äquivalent, sie legen im aktuellen Variablen-Geltungsbereich eine Funktion an:

function funktionsname () {
	window.alert("Dies ist eine Function Declaration.");
}
var funktionsname = function () {
	window.alert("Dies ist eine Function Expression in einem Variable Statement!");
};

Das Ergebnis der Function Expression ist das erzeugte Funktionsobjekt. Dies wird hier in einer Variable gespeichert.

Und was nützt dieses Wissen?

Nach diesen trockenen Beschreibungen seien einige Beispiele genannt. Moderne Scripte nutzen zunehmend die Einsicht in die JavaScript-Syntax, um Prozesse einfach und elegant umzusetzen. JavaScript-Anfänger sind manche Schreibweisen unbekannt, weil sich Lehrwerke sinnigerweise auf einfache und übliche Beispiele beschränken. Fortgeschrittene Programmierer übernehmen Schreibweisen, wissen aber häufig nicht, wie sie funktioniert und warum diese Schreibweisen erlaubt sind.

Function Expressions finden überall dort Einsatz, wo man schnell eine Funktion erzeugen will, ohne dass sie im aktuellen Geltungsbereich gespeichert werden muss. Ein Beispiel ist die Übergabe eines Funktionsobjektes an setTimeout oder die Zuweisung als Event-Handler:

window.setTimeout(function () {
	window.alert("Wird nach einer Sekunde ausgeführt");
}, 100);
element.onclick = function () {
	window.alert("Element wurde geklickt");
};

Function Expressions spielen ferner eine wichtige Rolle beim Arbeiten mit Closures.

Wir können anonyme Funktionen, die sofort ausgeführt werden, verwenden, um Teile des Scriptes zu kapseln:

(function () {
	var lokaleVariable = "Diese Variable ist nur in dieser anonymen Funktion verfügbar.";
})();

Diese Syntax mag obskur aussehen, ist aber leicht zerlegbar, wenn man den Aufbau von Statements und Expressions versteht: Es handelt sich um ein Expression Statement mit einer geklammerten Function Expression gefolgt von einem Call-Operator zum sofortigen Aufrufen der Funktion. (Die Parameterliste ist leer.)

Wir kennen die Anwendung von Schleifen nach dem Schema while (a > 5) .... Zwischen den Klammern steht eine Expression, die einen Boolean-Wert ergeben muss (wenn nicht, dann wird der Typ einfach automatisch umgewandelt). Wir sind es gewohnt, hier mit Variablen und Vergleichsoperatoren zu arbeiten. In manchen Fällen können wir jedoch völlig anders aufgebaute Expressions an dieser Stelle nutzen. Nehmen wir das Script Find position. Das Script steigt von einem Element im Knotenbaum nach oben und addiert die Offset-Werte der Elemente, an denen es vorbeiläuft.

function findPos(obj) {
	var curleft = curtop = 0;
	if (obj.offsetParent) {
		do {
			curleft += obj.offsetLeft;
			curtop += obj.offsetTop;
		} while (obj = obj.offsetParent);
	}
	return [curleft,curtop];
}

Anstelle eines Vergleiches findet man hier eine Expression mit einem Zuweisungsoperator. Ja, eine Zuweisung ist auch nur eine Expression und hat ein Ergebnis – nämlich den zugewiesen Wert. Solange obj.offsetParent auf ein Elementknoten zeigt, wird obj aktualisiert und die Schleife immer wieder ausgeführt (denn in Boolean umgewandelt ergibt ein Objekt true).

Beim obersten Elementknoten ergibt obj.offsetParent undefined. Somit ist das Ergebnis der Zuweisung undefined, das ist gleich false. An dieser Stelle bricht die Schleife also ab.

Möglich ist das ganze, weil zwischen den Klammern irgendeine Expression stehen kann. Bei for-Schleifen gilt dasselbe, das Schema lautet for (Expression; Expression; Expression) Statement.

Der folgende Code durchläuft eine Elementliste:

var elementList = document.getElementsByTagName("p");
for (var i = 0; i < elementList.length; i++) {
	var element = elementList[i];
	// Mach was mit element
}

Auch hier können wir die Zuweisung in den Schleifenkopf verlagern:

var elementList = document.getElementsByTagName("p");
for (var i = 0, element; element = elementList[i]; i++) {
	// Mach was mit element
}

Wenn i so groß wird, dass ihm kein Element mehr in der Knotenliste entspricht, dann ergibt elementList[i] einfach null, was gleich false ist. Die Schleife bricht also rechtzeitig ab.

Wenn man die aktuelle Zeit in Erfahrung bringen will, muss man üblicherweise mit new Date() ein Date-Objekt erzeugen. Will man anschließend sowieso nur eine Methode aufrufen und braucht das Objekt darüber hinaus nicht mehr, kann man ebenso in eine Zeile schreiben:

window.alert("Wir schreiben das Jahr " + (new Date).getFullYear());

Genauso kann man Literale direkt als Objekte behandeln:

// String-Literal
window.alert("test".toUpperCase());
// Literal für einen Regulären Ausdruck, erzeugt ein RegExp-Object
window.alert(/^[a-z]+$/i.test("abc"));

Am bekanntesten ist das Object-Literal, mit dem heutzutage viele Scripte organisiert werden. Dieses hat eine Eigenstruktur, die sich nicht allein mit den Begriffen Statement oder Expression beschreiben lassen. Es enthält aber an bestimmter Stelle Expressions, sehr häufig Function Expressions:

var container = {
	funktion : function () {
		window.alert("Function Expression");
	}
};
container.funktion();

Der logische Oder-Operator (||) hat in JavaScript die Eigenheit, dass er nicht einen Boolean-Wert zurückgibt, sondern denjenigen Operanden, der true ergibt, wenn er in Boolean umgewandelt wird. Auf die Weise können wir Standardwerte für Funktionsparameter umsetzen, falls diese nicht gesetzt sind:

function funktion (parameter) {
	parameter = parameter || "Standardwert";
	window.alert(parameter);
}
funktion("Hallo!");
funktion();

Häufige Verwendung findet dies bei Event-Handlern zum browserübergreifenden Zugriff auf das Event-Objekt:

function handler (e) {
	// Wenn der Browser das Event-Objekt nicht als Parameter übergeben hat,
	// lese es aus window.event (Hallo, Internet Explorer!)
	e = e || window.event;
	window.alert(e.type);
}

Christian Heilmann bietet weitere Beispiele in seinem englischsprachigen Artikel JavaScript shortcut notations that shouldn’t be black magic to the “average developer”, darunter der oben genannte äußerst nützliche Konditional-Operator.

Nun ist es nicht immer erstrebenswert, die kürzeste und unverständlichste Schreibweise zu verwenden. Aber das Wissen um Expressions kommt einem zu Gute, wenn Expressions zunehmend komplex werden und man deren Berechnung nachvollziehen will. Andererseits kann man Statements mit Expressions gekonnt mixen, wenn man weiß, dass beliebige Expressions an vielen Stellen erlaubt sind.

12 Kommentare » Schreibe einen Kommentar

  1. Hallo,

    erstens mal vielen Dank für diese Erklärungen 🙂

    eine Frage hätte ich allerdings auch und zwar handelt es sich um diese Zeile:

    (url.match(/http:\/\/www\.google\.\w+(\.\w+)?\/((search)|(custom))/i)!=null)

    ich möchte gerne das http://www.google.com mit http://localhost:4664 austauschen aber ich versthe nicht ganz warum da so viele "\" vorhanden sind und was das "w+(\.\w+)" bedeutet…

    vielen Dank für die Hilfe!

    grüße
    thomas

  2. Ich bitte so nett ich nur kann um Hilfeeeee !

    Zu meinem Problem hab ich folgendes definiert:

    ***Header***

    <script type="text/javascript">
    function anaushp{
    if(document.classes.hp.style.anz1.display==’none‘)
    window.document.classes.hp.style.anz1.display=’block‘;
    else document.classes.hp.style.anz1.display=’none‘;}
    </script>

    <STYLE TYPE="text/css"><!–
    .anz0 { display : none; }
    –></STYLE>

    ***body***

    <a href="javascript:anaushp;">anaushp</a>

    <DIV id="hp" class="anz0">
    TEXT .. TEXT … TEXT
    </DIV>

    Damit möchte ich in Abhängigkeit von einem Klick auf den Link die Anzeigen von dem DIV-Container Ein- und Auschalten können, das kruzefixding klappt aber nicht. Das müßte doch gehen … aber wie????

  3. @pother_dane:

    Nein, new Date ist eine erlaubte Abkürzung von new Date(). Wenn die Parameterliste ohnehin leer ist, müssen deren Klammern nicht sein (ECMAScript 11.2.2).

  4. Sehr guter Artikel.
    Für Einsteiger wie mich, für den "Details" wie Expression schon Geheimnischarakter haben, macht es einiges klarer. Danke dafür.

    Nebennbei:

    window.alert("Wir schreiben das Jahr " + (new Date).getFullYear());

    sollte wohl

    window.alert("Wir schreiben das Jahr " + (new Date()).getFullYear());

    meinen?

    p_d

  5. Achja, Mataphern haben wie Ironie im Web den Nachteil, dass man diese nicht so leicht erkennt und häufig zu wörtlich nimmt.

  6. Beim Phi-Beispiel gibt auch keinen wirklichen Vorteil. War ja nur nen Beispiel.

    Vielleicht sieht mans so besser:

    phi = function () {
      var d = 1;
      for (i=0;i<100000;i++)
        d = (1+1/d);
      return d;
    }();
    alert (phi);
    

    Auch das kann man natürlich anders schreiben. Ich wollte ja nur demonstrieren, dass es diese Schreibweise gibt.

  7. @Jonathan: Das nennt man Metapher. Geheimnisse sind es, weil sie vielen unbekannt sind und ihr Kennenlernen nützlich sein kann. Natürlich sind es keine wirklichen Geheimnisse – die gibt’s nicht im Bereich von offenen Webstandards. Und ich mache hier keinen Investigativjournalismus, sondern prosaische technische Dokumentation. Trotzdem behalte ich mir die rhetorische Zuspitzung und pointierte Formulierung von Überschriften vor. 😉

    Dein phi-Beispiel verstehe ich nicht, ich sehe da keinen Vorteil bei der Function-Variante.

  8. Stimmt. Hab ich wohl irgendwie verwechselt.

    a = function () {
    	var lokaleVariable = "Diese Variable ist nur in dieser anonymen Funktion verfügbar.";
    }();

    funktioniert aber zum Beispiel ist aber Aufgrund der nicht vorhandenen Rückgabe auch nicht ideal.

    Vernünftiger wäre z.B.:

    phi = function () {
      var d = Math.sqrt(5)+1;
      return d/2;
    }();

    Auch dass ließe sich natürlich als phi = (Math.sqrt(5)+1)/2; schreiben, aber irgendwann, wenn man auch noch Schleifen usw. einbaut, wird die separate Funktion besser.

  9. Hallo Jonathan,

    Deine Variante ohne Klammern liefert im Firefox einen Syntax-Error (andere Browser habe ich jetzt nicht getestet). Damit ist die geklammerte Variante schon korrekt.

  10. Super Artikel.

    Nur die Überschrift „Geheimnisse der JavaScript-Syntax“ find ich jetzt nicht so gelungen. Das sind ja keine wirklichen Geheimnisse und wir sind hoffentlich nicht auf Bild oder Chip-Niveau.

    Anstatt

    (function () {
    	var lokaleVariable = "Diese Variable ist nur in dieser anonymen Funktion verfügbar.";
    })();
    

    hätte man aber auch

    function () {
    	var lokaleVariable = "Diese Variable ist nur in dieser anonymen Funktion verfügbar.";
    }();
    

    schreiben können. Finde das wesenlich übersichtlicher und man hat eine Klammer gespart. Man schreibt ja auch nicht

    (alert)("Hallo Welt");

    obwohl das Syntaktisch möglich wäre.

  11. Toller Artikel! Solche grundsätzlichen und vor allem praktisch programmierlogischen Schritte werden (wie auch schon im Artikel erwähnt) oft viel zu wenig in den Unmengen an Lehrbüchern beachtet.

    Danke für diese Ausführlichkeit!