JavaScript – Das Handbuch für die Praxis. David Flanagan
bezeichnet, weil sie auch für längere, »verkettete« Ausdrücke für den Eigenschaftszugriff wie diesen funktioniert:
let a = { b: null };
a.b?.c.d // => undefined
a ist ein Objekt, also ist a.b ein gültiger Ausdruck für den Eigenschaftszugriff. Aber der Wert von a.b ist null, sodass a.b.c einen TypeError auslösen würde. Durch die Verwendung von ?. anstelle von . vermeiden wir diesen TypeError, und a.b?.c wird zu undefined ausgewertet. Das wiederum bedeutet, dass (a.b?.c).d einen TypeError auslöst, weil dieser Ausdruck versucht, auf eine Eigenschaft mit dem Wert undefined zuzugreifen. Aber – und das ist entscheidend bei der optionalen Verkettung – a.b?.c.d (ohne die Klammern) ergibt einfach undefined und löst keinen Fehler aus. Das liegt daran, dass der Zugriff auf Eigenschaften mit ?. vorzeitig unterbrochen wird (im Englischen wird dabei häufig von short-circuiting gesprochen, also »kurzschließen«): Wenn der Unterausdruck links von ?. zu null oder undefined ausgewertet wird, dann wird der gesamte Ausdruck sofort zu undefined ausgewertet, ohne dass überhaupt noch versucht wird, auf die nachfolgend angegebenen Eigenschaften zuzugreifen.
Wäre a.b ein Objekt und besäße dieses Objekt keine Eigenschaft namens c, würde a.b?.c.d natürlich wieder einen TypeError auslösen – und wir müssten einen weiteren bedingten Eigenschaftszugriff verwenden:
let a = { b: {} };
a.b?.c?.d // => undefined
Der bedingte Eigenschaftszugriff ist auch mit ?.[] anstelle von [] möglich. Wenn im Ausdruck a?.[b][c] der Wert von a gleich null oder undefined ist, wird der gesamte Ausdruck sofort zu undefined ausgewertet, ohne dass bei den Teilausdrücken b und c eine Auswertung überhaupt noch versucht wird. Weist einer dieser Ausdrücke Nebeneffekte auf, treten diese nicht auf, wenn a gar nicht definiert ist:
let a; // Hoppla, wir haben vergessen, diese Variable zu initialisieren!
let index = 0;
try {
a[index++]; // Löst einen TypeError aus.
} catch(e) {
index // => 1: Inkrementierung erfolgt, bevor der TypeError
// ausgelöst wird.
}
a?.[index++] // => undefined: weil a undefined ist.
index // => 1: Nicht erneut inkrementiert, weil ?.[] vorzeitig
// unterbrochen ("kurzgeschlossen") hat.
a[index++] // !TypeError: undefined kann nicht indiziert werden.
Der bedingte Eigenschaftszugriff mit ?. und ?.[] ist eine der neuesten Funktionen von JavaScript. Stand Anfang 2020 wird diese neue Syntax in den aktuellen oder Betaversionen der meisten gängigen Browser unterstützt.
4.5Aufrufausdrücke
Mit einem Aufrufausdruck ruft man in JavaScript eine Funktion oder Methode auf bzw. führt sie aus. Er beginnt mit einem Funktionsausdruck, der die aufzurufende Funktion bezeichnet. Auf den Funktionsausdruck folgen eine öffnende Klammer, eine kommagetrennte Liste mit null oder mehr Argumenten und eine schließende Klammer. Ein paar Beispiele:
f(0) // f ist der Funktionsausdruck, 0 der Argumentausdruck.
Math.max(x,y,z) // Math.max ist die Funktion, x, y und z sind die Argumente.
a.sort() // a.sort ist die Funktion, es gibt keine Argumente.
Wird ein Aufrufausdruck ausgewertet, wird zuerst der Funktionsausdruck ausgewertet. Anschließend werden die Argumentausdrücke ausgewertet, um eine Liste mit Argumentwerten zu erstellen. (Ist der Wert des Funktionsausdrucks keine Funktion, wird ein TypeError ausgelöst.) Danach werden die Argumentwerte der Reihe nach den Parameternamen zugewiesen, die in der Definition der Funktion angegeben sind, bevor anschließend der Code im Körper der Funktion ausgeführt wird. Nutzt die Funktion eine return-Anweisung, um einen Wert zurückzuliefern, wird dieser zum Wert des Aufrufausdrucks. Andernfalls ist der Wert des Aufrufausdrucks undefined. Sämtliche Informationen zum Funktionsaufruf – auch dazu, was geschieht, wenn die Anzahl von Argumentausdrücken nicht mit der Anzahl der Parameter in der Funktionsdefinition übereinstimmt – finden Sie in Kapitel 8.
Jeder Aufrufausdruck enthält ein Klammernpaar, vor dem ein Ausdruck steht. Greift der Ausdruck vor den Klammern auf eine Eigenschaft zu, bezeichnet man den Aufruf selbst als Methodenaufruf. Bei Methodenaufrufen wird das Objekt oder Array, das Gegenstand des Eigenschaftszugriffs ist, während der Ausführung des Codes im Funktionskörper zum Wert des this-Schlüsselworts. Das ist das Fundament des objektorientierten Programmierparadigmas, bei dem Funktionen als »Methoden« bezeichnet werden und auf dem Objekt operieren, zu dem sie gehören. Einzelheiten dazu finden Sie in Kapitel 9.
4.5.1Bedingter Aufruf
Ab ES2020 können Sie eine Funktion auch mit ?.() anstelle von () aufrufen. Normalerweise wird beim Aufruf einer Funktion ein TypeError ausgelöst, wenn der Ausdruck links von der Klammer null oder undefined lautet oder keine Funktion ist. Bei der neuen Aufrufsyntax der Form ?.() wird der gesamte Aufrufausdruck zu undefined ausgewertet, wenn der Ausdruck links vom ?. zu null oder undefined ausgewertet wird, und zudem wird keine Ausnahme ausgelöst.
Array-Objekte verfügen über eine sort()-Methode, der optional ein Funktionsargument übergeben werden kann, das die gewünschte Sortierreihenfolge für die Array-Elemente festlegt. Wollte man vor ES2020 eine Methode wie sort() schreiben, die ein optionales Funktionsargument benötigt, hätte man normalerweise mit einer if-Anweisung überprüft, ob das Funktionsargument definiert wurde. Erst danach hätte man es dann im Funktionskörper aufgerufen:
function square(x, log) { // Das zweite Argument ist eine optionale Funktion.
if (log) { // Falls die optionale Funktion übergeben wird,
log(x); // wird sie aufgerufen.
}
return x * x; // Das Quadrat des Arguments wird zurückgegeben.
}
Mit der bedingten Aufrufsyntax von ES2020 können Sie den Funktionsaufruf jedoch einfach mit ?.() formulieren. Damit stellen Sie sicher, dass der Aufruf nur dann erfolgt, wenn es auch tatsächlich einen aufzurufenden Wert gibt:
function square(x, log) { // Das zweite Argument ist eine optionale Funktion.
log?.(x); // Rufen Sie die Funktion auf, falls es eine gibt.
return x * x; // Das Quadrat des Arguments wird zurückgegeben.
}
Denken Sie aber daran, dass ?.() nur prüft, ob die linke Seite null oder undefined ist, aber nicht, ob der Wert tatsächlich eine Funktion ist. Die square()-Funktion des obigen Beispiels würde also immer noch eine Ausnahme auslösen, wenn Sie ihr z.B. zwei Zahlen übergeben (anstelle einer Zahl und einer Funktion).
Wie die Ausdrücke für den bedingten Eigenschaftszugriff (siehe 4.4.1) ist der Funktionsaufruf mit ?.() »kurzschließend« (short-circuiting): Die Auswertung wird vorzeitig unterbrochen, wenn der Wert links von ?. null oder undefined lautet. Keiner der Argumentausdrücke innerhalb der Klammern wird dann noch ausgewertet:
let f = null, x = 0;
try {
f(x++); // Löst einen TypeError aus, weil f null ist.
} catch(e) {
x // => 1: x wurde inkrementiert, bevor die Ausnahme ausgelöst wurde.
}
f?.(x++)