JavaScript/Syntax
Ein JavaScript-Programm ist nach gewissen Regeln aufgebaut. Solche Syntax-Regeln kennt man aus der natürlichen Sprache, zum Beispiel dem Deutschen. Dabei müssen beim Formulieren eines Satzes gewisse Grundregeln der Grammatik eingehalten werden, damit man verstanden wird.
Glücklicherweise verstehen uns unsere Mitmenschen auch dann, wenn wir kleinere Fehler machen und uns nur grob an die Regeln halten. Bei Mehrdeutigkeiten ist oft aus der Situation ersichtlich, was gemeint ist.
Programmiersprachen sind jedoch viel strenger: Ihre Regeln sind vergleichsweise einfach, eindeutig und lassen wenig Spielraum.
Dies hat folgende Bewandnis: Damit der JavaScript-Interpreter ein Programm ausführen kann, muss er zunächst dessen Syntax verstehen. Das Zerlegen des Programms in seine Bestandteile nennt sich Parsing. Das Modul des Interpreters, das dafür zuständig ist, nennt sich Parser. Wenn der Interpreter dabei auf einen Syntaxfehler trifft, bricht er mit einer Fehlermeldung ab und das JavaScript-Programm wird gar nicht erst ausgeführt.[1]
Dieser Artikel soll eine Referenz der Sprachelemente und der Syntax von JavaScript zur Verfügung stellen, wobei vieles in den Folgekapiteln erklärt wird.
Inhaltsverzeichnis
Grundsätzliches
JavaScript ist eine Programmiersprache, die als Zusatztechnik in Webseiten eingebunden wird. Im modernen Webdesign erhalten Webseiten so neben der Inhaltsstruktur und dem Aussehen eine Verhaltensschicht.
JavaScript kommt in diesem Konzept die Aufgabe zu, dem Dokument »Verhalten« (behaviour) hinzuzufügen. Damit ist gemeint, dass das Dokument auf gewisse Anwenderereignisse reagiert und z. B. Änderungen im Dokument vornimmt.
→ Hauptartikel: Einstieg in JavaScript
Dort findet sich auch ein Tutorial, das dir die ersten Schritte anhand von Live-Beispielen näher bringt.
Einbindung in Webseiten
JavaScript-Quelltexte werden in HTML in einem script-Element notiert oder referenziert. Das script
-Element darf dabei im body
oder head
des HTML-Dokuments notiert werden, auch innerhalb von flow content.
<script> … </script>
→ Hauptartikel: JavaScript in HTML einbinden
Der „strenge Modus“
In JavaScript gibt es aufgrund der bewegten Geschichte mit vielen proprietären Methoden und der sehr einfachen Möglichkeit, globale Variablen einzuführen, viele potentielle Fehlerquellen. Deshalb wurde mit ECMA5 der Strenge Modus (strict mode) eingeführt, der ein standardisiertes, um Fehlerquellen bereinigtes, Subset der Programmiersprache aktiviert.
'use strict';
...
Die folgenden Artikel gehen davon aus, dass der strenge Modus aktiviert ist. So wird man gezwungen, modernen und weniger fehleranfälligen Code zu schreiben.
→ Hauptartikel: JavaScript/strict mode
Anweisung
Ein JavaScript-Programm besteht aus einer Abfolge von Anweisungen (Statements).[2]
Eine Anweisung stellt eine in der Syntax einer Programmiersprache formulierte einzelne Vorschrift dar, die im Rahmen der Abarbeitung des Programms auszuführen ist.Wikipedia: Anweisung (Programmierung)[3]
Aussagen entsprechen in etwa den Sätzen in natürlichen Sprachen. Eine Anweisung bildet eine vollständige Einheit der Ausführung.[4]
alert('Hello world!');
In dieser Anweisung wird die alert()-Methode verwendet, um eine Zeichenkette in einem Dialogfenster auszugeben. Das Semikolon markiert ausdrücklich das Ende der Anweisung.
const secondsInADay = 86400;
Diese Anweisung ist eine Variablendeklaration, in der der Wert 86400 der Konstanten secondsInADay
zugewiesen wird.
let fläche = laenge * breite;
In JavaScript kann eine Anweisung eine Deklaration sein, ein Ausdruck oder eine Kontrollstruktur (Verzweigungen und Schleifen).
Anweisungen können in einer einzigen Programmzeile aufgeschrieben werden, in den meisten Fällen aber auch auf mehrere Zeilen verteilt werden, um die Lesbarkeit des Programms zu verbessern. Man kann auch mehrere Anweisungen auf eine Programmzeile schreiben (sollte das aber unterlassen, weil die Lesbarkeit des Programms darunter leidet).
Anweisungsblock
Ein Anweisungsblock besteht aus einer oder mehreren Anweisungen, die innerhalb einer übergeordneten Anweisung oder innerhalb einer Funktion stehen.[5]
Ein Anweisungsblock wird durch eine öffnende geschweifte Klammer {
begonnen und durch eine schließende geschweifte Klammer }
beendet.
function greet() {
let message = 'Hello world!';
console.log(message);
}
Eine Funktion enthält einen Block von Anweisungen, die ausgeführt werden, wenn die Funktion aufgerufen wird.
let count = 0;
while (count < 5) {
console.log('Durchlauf ' + count);
count++;
}
Man kann die geschweiften Klammern jeweils in eine eigene Zeile schreiben, wie in den obigen Beispielen. Es ist aber auch erlaubt, die Klammern in derselben Zeile zu notieren wie die Anweisungen.
if (number > 5) {
console.log('Die Zahl ist größer als 5.');
console.log(' Dieser Anweisungsblock wird ausgeführt.');
} else { console.log('Die Zahl ist 5 oder kleiner.'); }
So können Anweisungsblöcke beispielsweise unterhalb einer bedingte Anweisung oder einer Schleife stehen.
Auch alle Anweisungen, die innerhalb einer selbst definierten Funktion stehen, bilden einen Anweisungsblock.
Das Semikolon
Grundsätzlich ist es so, dass Anweisungen durch ein Semikolon ;
abgeschlossen werden müssen. Diese Vorschrift ermöglicht erst das Verteilen einer Anweisung auf mehrere Zeilen, andernfalls wäre ein Programm nicht eindeutig interpretierbar.
Es gibt aber Ausnahmen:
- Funktionsdeklarationen und manche Kontrollstrukturen enden mit einem Anweisungsblock. Hinter dessen Abschlussklammer wird kein Semikolon gesetzt.
- JavaScript war als simple Sprache konzipiert, die ihren Benutzern Fehler verzeihen sollte. Deshalb ergänzt JavaScript an einigen Stellen automatisch ein Semikolon, wenn es glaubt, dass dort eins hingehört, und das führt dazu, dass JavaScript an einigen Stellen verbietet, eine Anweisung auf mehrere Zeilen aufzuteilen.
Automatische eingefügte Semikolons
Automatisch eingefügte Semikolons sind eine Eigenschaft des JavaScript-Parsers. Wenn er nach einem Zeilenumbruch auf ein }
-Zeichen stößt oder auf ein Token, das auf Grund der Sprachsyntax an dieser Stelle nicht erwartet wird, dann fügt er vor dem Zeilenumbruch ein Semikolon ein. Das Ende des Quelltexts wird in diesem Zusammenhang übrigens ebenfalls als ein unerwartetes Token aufgefasst.
Darüber hinaus fügt der Parser Semikolons an den folgenden Stellen ein:
- Hinter dem Schlüsselworten break, continue, throw, return, yield und async, wenn in der gleichen Zeile nur noch Leerstellen folgen
- Zwischen der Parameterangabe und dem Pfeilsymbol einer Pfeilfunktion
- Zwischen einem nachgestellten Inkrementor oder Dekrementor und seinem Operanden
Es gibt Ausnahmen: Innerhalb der Klammern der for-Schleife werden keine Semikolons eingefügt, und wenn das Semikolon eine leere Anweisung erzeugen würde, wird ebenfalls keins eingefügt.
Eine weitere Ausnahme entsteht, wenn sich aus dem unerwarteten Token irgendwie syntaktisch gültiger Code herausdeuten lässt. Bei Mozilla findet man diese Beispiele[6]:
const a = 1
(1).toString()
const b = 1
[1,2,3].forEach(console.log)
1[1,2,3]
als Zugriff auf eine Eigenschaft deuten. Der Inhalt der eckigen Klammern wird als Aneinanderreihungsoperator ,
verstanden, dessen Wert der letzte Operand der Aneinanderreihung ist, also 3. JavaScript möchte also die Eigenschaft "3" der Zahl 1 abrufen - die es nicht gibt, weshalb das Ergebnis undefined
ist. Und weil undefined
keine forEach
-Methode kennt, erhält man die Fehlermeldung Cannot read properties of undefined (reading 'forEach').Der Sinn dieses merkwürdigen Verhaltens war, wegen eines vergessenen Semikolons nicht gleich das Programm abbrechen zu lassen. Der Programmtext sollte sinnvoll interpretiert werden. Bei all diesen Ausnahmen bleibt aber nur – frei nach Douglas Adams – eines zu sagen:
We apologise for the convenienceSo Long, and Thanks for All the Quirks
Zu Risiken und Nebenwirkungen …
JavaScript ist, wie alle Sprachen der C-Familie, sehr stark auf die Verwendung von Ausdrücken ausgerichtet. Ein Ausdruck ist ein Sprachkonstrukt, das Werte und Operatoren benutzt, um einen neuen Wert zu ermitteln.
Manche Anweisungen bestehen nur aus einem Ausdruck:
Math.pow(2, 10); // What is 2^10? Just checking!
In der Konsole kann so eine schnelle Berechnung angezeigt werden.
typeof "Hello"; // What's the type of a string?
Mit typeof kann der Datentyp ermittelt werden.
10 * 3; // nur Berechnung
Hier dient der mathematische Ausdruck nur als Zwischenschritt.
In Realität würde so etwas als Zuweisung realisiert werden.
const result = 10 * 3;
Das Ergebnis des Ausdrucks wird der Konstanten result
über den Zuweisungsoperator =
zugewiesen und kann im weiteren Verlauf des Programms immer wieder aufgerufen und weiterverwendet werden. Dies nennt man Wirkung oder in einer Rückübersetzung von side effect auch Seiteneffekt.
Weitere Operatoren mit Seiteneffekten sind Inkrement und Dekrement und delete.
let count++;
Mit dem Post-Inkrement Operator ++
wird der Wert der Variablen count
um 1 erhöht. Diese Variable kann dann z. B. in einer Schleife weiterverwendet werden.
Seiteneffekte können auch auftreten, wenn man Programmierschnittstellen des Browsers aufruft. Die Wirkung kann sein, dass sich das angezeigte Dokument verändert, Daten von einem Server abgeholt werden oder dass eine Funktion aufgerufen wird, sobald ein bestimmtes Ereignis eintritt.
nameElement.addEventListener('click', handleNameClick);
Für das Ereignis 'click' wird eine Behandlungsfunktion registriert. Ein Rückgabewert wird nicht erwartet, dieser Ausdruck hat den Seiteneffekt, dass eine Funktion in einer Liste von Ereignisbehandlern eingetragen wird.
Der Begriff Seiteneffekte bezeichnet also etwas völlig neutrales und zielt auf die beabsichtigte Wirkung der Anweisung auf weitere, nachfolgende Stellen im Programm.
Seiteneffekte vorhersehen
Woher soll man nun wissen, ob Aufrufe wie document.getElementById
oder htmlElement.addEventListener
Seiteneffekte haben oder nicht? Hier hilft nur Handbuchstudium (das SELFHTML-Wiki ist ebenfalls gerne behilflich). Du musst die Beschreibung der jeweiligen Funktion lesen und verstehen, welche Aufgabe sie erfüllt. Zumeist ist auch der Name der Funktion eine Hilfe:
getElementById oder addEventlistener sagen deutlich, ob hier ein Seiteneffekt eintritt oder nicht. Mit etwas Programmiererfahrung weiß man dann, bei welchen Funktionen Seiteneffekte erwarten werden müssen und bei welchen nicht.
Das letzte Beispiel ruft die Funktion motorSteuerung
auf. Sie hat irgendetwas mit einer Motorsteuerung zu tun. Ob sie Seiteneffekte hat? Soll sie den linken Motor auf Leistung 5 einstellen? Soll sie die Drehzahl des fünften Motors auf der linken Seite auslesen? Wir wissen es nicht.
motorSteuerung("links", 5);
Eine Funktion wie motorSteuerung
ist ein Beispiel für eine schlecht benannte Funktion. Ein Name wie setMotorPower
oder getMotorSpeed
wäre deutlich hilfreicher gewesen. So bleibt uns nur das Handbuch, oder bei einer selbstgeschriebenen und undokumentierten Funktion das Studium des Programmcodes.
Selbstvergebene Namen
An vielen Stellen in JavaScript musst du selbst Namen vergeben, zum Beispiel für selbst definierte Funktionen, eigene Objekte oder Variablen. Diese werden Bezeichner (engl. Identifier) genannt.[7]
Bei selbst vergebenen Namen gelten folgende Regeln:
- sie dürfen keine Leerzeichen oder
-
enthalten (würde als Operator erkannt werden) - sie dürfen aus Unicode-Zeichen und Ziffern (0-9) bestehen - das erste Zeichen darf aber keine Ziffer sein; Groß- und Kleinbuchstaben sind erlaubt und werden unterschieden!
- sie dürfen als einzige Sonderzeichen
$
und den Unterstrich "_" enthalten - sie dürfen deutsche Umlaute oder ein scharfes
ß
enthalten, dabei ist aber zu beachten, dass andere Tastaturen diese Zeichen nicht haben,
- sie dürfen als einzige Sonderzeichen
- sie dürfen nicht mit einem reservierten Wort identisch sein.
Im europäischen und nordamerikanischen Raum beschränken sich die meisten JavaScript-Programmierer auf lateinische Buchstaben ohne diakritische Zeichen. Der Bezeichner größe
ist zwar möglich. Üblicher ist allerdings, groesse
zu notieren, um Schwierigkeiten bei der Zeichenkodierung aus dem Weg zu gehen. Im internationalen, kommerziellen Umfeld werden meistens englischsprachige Bezeichner verwendet.
Es dürfen ruhig auch deutschsprachige Wörter sein.
Da keine Bindestriche erlaubt sind, verwendet man vorzugsweise CamelCase-Schreibweise.
Reservierte Wörter
Es gibt eine Reihe von reservierten Wörtern (Reserved Words), die nicht als Name für einen Bezeichner verwendet werden dürfen. Andernfalls wird ein Syntaxfehler erzeugt, der die Ausführung des Programms verhindert. Es ist deshalb bei der Vergabe von Bezeichnern, etwa für Funktionen oder Variablen, stets darauf zu achten, dass es sich bei dem gewählten Namen nicht um ein reserviertes Wort handelt.[8][9]
// Syntax Error
function break() {
return null;
}
Wird wie bei der Funktionsdeklaration in dem Beispiel oben ein reserviertes Wort als Name für den Bezeichner gewählt, in diesem Fall also das Schlüsselwort break, dann wird das Programm erst gar nicht ausgeführt. Diese Funktion macht ihrem Namen also alle Ehre.
- as
- -Alias beim Export von Modulen, bzw. deren Inhalten
- case
- Für Fallunterscheidungen mit switch.
- catch
- Wird bei der Fehlerbehandlung mit try und catch verwendet.
- const
- Leitet die Deklaration einer oder mehrerer Konstanten ein.
- continue
- Teil einer Fortsetzungsanweisung.
- debugger
- Definiert einen Haltepunkt im Programm für Debugger.
- default
- Standardklausel bei switch. Auch verwendet bei Standardexport in einem Modul.
- delete
- Operator zum Löschen von Objekteigenschaften.
- do
- Für Schleifen mit do und while.
- else
- Teil einer bedingten Anweisung mit if.
- export
- Wird bei der Deklaration eines Exports in einem Modul verwendet.
- extends
- Mit dem Schlüsselwort extends kann eine abgeleitete Klasse definiert werden.
- finally
- Kann bei der Fehlerbehandlung mit try und catch verwendet werden.
- for
- Wird in verschiedenen Schleifen verwendet.
- function
- Schlüsselwort zur Definition einer Funktion.
- if
- Für bedingte Anweisungen.
- implements *
- import
- Wird verwendet bei der Deklaration eines Imports aus einem Modul.
- in
- Operator in einem relationalen Ausdruck. Auch verwendet in einer Schleife mit for.
- instanceof
- Hiermit kann geprüft werden, ob ein Objekt eine Instanz eines Konstruktors ist.
- interface *
- new
- Operator für den Konstruktorenaufruf.
- package *
- private *
- protected *
- public *
- return
- Teil der Rückgabeanweisung einer Funktion.
- super
- Schlüsselwort zum Methoden- oder Konstruktorenaufruf in Objekt oder abgeleiteter Klasse.
- switch
- Für Fallunterscheidungen.
- this
- Eingebaute Kontextvariable von Funktionen.
- throw
- Erzeugt eine benutzerdefinierte Ausnahme.
- try
- Wird bei der Fehlerbehandlung mit try und catch verwendet.
- typeof
- Operator, der den Datentyp des Operanden zurückgibt.
- var
- Schlüsselwort für die Deklaration einer oder mehrerer Variablen.
- void
- Wertet den Ausdruck auf der rechten Seite aus und gibt standardmäßig undefined zurück.
- while
- Wird bei Schleifen verwendet.
- with
- wurde früher verwendet, um mehrere Anweisungen mit einem Objekt durchzuführen.
- yield
- Pausiert die Ausführung einer Generatorfunktion.
* Für die zukünftige Entwicklung der Sprache reservierte Wörter (Future Reserved Words). Sie sind nur dann reserviert, wenn das Programm im Strict Mode ausgeführt wird. Jedoch gilt auch hier, dass von der Verwendung diese Wörter als Namen für Bezeichner von Variablen, Konstanten oder Funktionen abzuraten ist, selbst wenn das im normalen Modus möglich wäre.
ToDo (weitere ToDos)
Sollte man nicht oben im Kapitel den strict mode erwähnen und empfehlen und hier nur eine Anmerkung machen
* gilt nur im strict mode
Es sind allerdings in Bezug auf die reservierten Wörter einige Besonderheiten zu beachten, die nicht unbedingt sofort ersichtlich sind.
const object = {
delete(property) {
return delete this[property];
}
};
object.default = 'value';
console.info(object.delete('default')); // true
So sind die Namen von Objekteigenschaften und Methoden (Property Names) zum Beispiel grundsätzlich keine Bezeichner im Sinne der Sprache, weshalb das Verbot der Verwendung reservierter Wörter hier nicht einschlägig ist. Dabei spielt es auch keine Rolle, ob eine Eigenschaft oder Methode in einem Objektliteral definiert wird oder nicht, weshalb in dem Beispiel oben durch die Verwendung der Schlüsselwörter delete und default als Eigenschafts- beziehungsweise Methodenname kein Fehler erzeugt wird.
let public = 'valid';
// Syntax Error
function test() {
'use strict';
const static = 'invalid';
}
Darüber hinaus ist zu beachten, dass manche Wörter nur im Strict Mode reserviert sind. So erzeugt etwa die Verwendung des Namens public als Bezeichner bei der Variablendeklaration in dem Beispiel oben keinen Fehler, da der globale Kontext im normalen Modus ausgeführt wird. Weil die Funktion test jedoch im Strict Mode ausgeführt werden soll und in ihr eine Konstante deklariert wird, deren Name in diesem Modus reserviert ist, wird hier dennoch ein Syntaxfehler erzeugt.
<script>
let await = 'Godot';
</script>
Schließlich kann der Status als reserviertes Wort auch davon abhängen, ob der auszuführende Code Teil eines Scripts oder Teil eines Moduls ist. Wird etwa, wie in dem Beispiel oben, eine Variable innerhalb eines Scripts deklariert und dabei der Bezeichner await verwendet, dann ist das kein Problem. Jedoch würde es sich anders verhalten, wenn die Deklaration innerhalb eines Moduls vorgenommen worden wäre, denn dort wäre der Name await reserviert.
Weblinks
- ↑ JavaScript: Syntax-Grundlagen von molily
- ↑ MDN: Statements and declarations
- ↑ Wikipedia: Anweisung (Programmierung), abgerufen am 27.04.2023
- ↑ Expressions, Statements, and Blocks (docs.oracle.com)
- ↑ MDN: Block statement
- ↑ MDN: Automatic semicolon insertion
- ↑ MDN: Identifier
- ↑ ECMAScript 2017 (7th Edition, ECMA-262 Draft): Reserved Words
- ↑ MDN: Keywords
1(1)
lässt sich so deuten, als wolle man die Zahl 1 als Funktion aufrufen. Dies ist inhaltlicher Unsinn, aber syntaktisch ist es richtig. Der Funktionsaufruf-Operator()
erwartet laut Sprachdefinition irgendeinen Wert, auf den er angewendet wird. Ob dieser Wert eine Funktion ist, stellt sich erst im Moment der Ausführung heraus. Du erhältst deshalb die Fehlermeldung 1 is not a function.