Zum Hauptinhalt springen

Optionale Verkettung

· 5 Minuten Lesezeit
Maya Armyanova ([@Zmayski](https://twitter.com/Zmayski)), Brecherin optionaler Verkettungen

Lange Reihen von Eigenschaftszugriffen in JavaScript können fehleranfällig sein, da jeder von ihnen den Wert null oder undefined (auch als „nullish“-Werte bekannt) ergeben könnte. Das Überprüfen der Existenz von Eigenschaften auf jedem Schritt verwandelt sich leicht in eine tief verschachtelte Struktur von if-Anweisungen oder eine lange if-Bedingung, die die Eigenschaftszugriffsreihe repliziert:

// Fehleranfällig, könnte eine Ausnahme werfen.
const nameLength = db.user.name.length;

// Weniger fehleranfällig, aber schwerer zu lesen.
let nameLength;
if (db && db.user && db.user.name)
nameLength = db.user.name.length;

Das oben genannte kann auch unter Verwendung des ternären Operators ausgedrückt werden, was die Lesbarkeit nicht gerade verbessert:

const nameLength =
(db
? (db.user
? (db.user.name
? db.user.name.length
: undefined)
: undefined)
: undefined);

Einführung des Operators für optionale Verkettung

Natürlich möchten Sie keinen solchen Code schreiben, daher ist eine Alternative wünschenswert. Einige andere Sprachen bieten eine elegante Lösung für dieses Problem mit einer Funktion namens „optionale Verkettung“. Laut einem aktuellen Spezifikationsvorschlag ist „eine optionale Verkettung eine Kette aus einem oder mehreren Eigenschaftszugriffen und Funktionsaufrufen, von denen der erste mit dem Token ?. beginnt“.

Mit dem neuen Operator für optionale Verkettung können wir das obige Beispiel wie folgt umschreiben:

// Überprüft weiterhin auf Fehler und ist viel besser lesbar.
const nameLength = db?.user?.name?.length;

Was passiert, wenn db, user oder name undefined oder null ist? Mit dem Operator für optionale Verkettung initialisiert JavaScript nameLength auf undefined, anstatt eine Ausnahme zu werfen.

Beachten Sie, dass dieses Verhalten auch robuster ist als unsere Überprüfung auf if (db && db.user && db.user.name). Was wäre zum Beispiel, wenn name immer garantiert ein String wäre? Wir könnten name?.length auf name.length ändern. Wenn name dann ein leerer String wäre, würden wir dennoch die korrekte Länge 0 erhalten. Denn der leere String ist ein falsy-Wert: Er verhält sich wie false in einer if-Klausel. Der Operator für optionale Verkettung behebt diese häufige Fehlerquelle.

Zusätzliche Syntaxformen: Aufrufe und dynamische Eigenschaften

Es gibt auch eine Version des Operators für optionale Methodenaufrufe:

// Erweitert die Schnittstelle mit einer optionalen Methode,
// die nur für Admin-Benutzer vorhanden ist.
const adminOption = db?.user?.validateAdminAndGetPrefs?.().option;

Die Syntax kann unerwartet wirken, da ?.() der tatsächliche Operator ist, der auf den Ausdruck davor angewendet wird.

Es gibt eine dritte Verwendung des Operators, nämlich den optionalen Zugriff auf dynamische Eigenschaften, der über ?.[] erfolgt. Er gibt entweder den durch das Argument in den Klammern referenzierten Wert zurück oder undefined, falls kein Objekt vorhanden ist, von dem der Wert abgerufen werden kann. Hier ist ein möglicher Anwendungsfall, basierend auf dem obigen Beispiel:

// Erweitert die Fähigkeiten des statischen Eigenschaftszugriffs
// mit einem dynamisch generierten Eigenschaftsnamen.
const optionName = 'optionale Einstellung';
const optionLength = db?.user?.preferences?.[optionName].length;

Diese letzte Form ist auch zum optionalen Indizieren von Arrays verfügbar, z. B.:

// Wenn das `usersArray` `null` oder `undefined` ist,
// wird `userName` auf elegante Weise auf `undefined` ausgewertet.
const userIndex = 42;
const userName = usersArray?.[userIndex].name;

Der Operator für optionale Verkettung kann mit dem Nullwert-verschmelzenden ?? Operator kombiniert werden, wenn ein nicht-undefined Standardwert benötigt wird. Dies ermöglicht einen sicheren Zugriff auf tiefe Eigenschaften mit einem angegebenen Standardwert und adressiert einen häufigen Anwendungsfall, für den früher benutzerdefinierte Bibliotheken wie lodash's _.get erforderlich waren:

const object = { id: 123, names: { first: 'Alice', last: 'Smith' }};

{ // Mit lodash:
const firstName = _.get(object, 'names.first');
// → 'Alice'

const middleName = _.get(object, 'names.middle', '(kein zweiter Name)');
// → '(kein zweiter Name)'
}

{ // Mit optionaler Verkettung und Nullwert-Verschmelzung:
const firstName = object?.names?.first ?? '(kein erster Name)';
// → 'Alice'

const middleName = object?.names?.middle ?? '(kein zweiter Name)';
// → '(kein zweiter Name)'
}

Eigenschaften des Operators für optionale Verkettung

Der Operator für optionale Verkettung hat einige interessante Eigenschaften: Kurzschluss, Stapeln und optionale Löschung. Lassen Sie uns jede dieser Eigenschaften mit einem Beispiel durchgehen.

Kurzschluss bedeutet, den Rest des Ausdrucks nicht auszuwerten, wenn ein Operator für optionale Verkettung frühzeitig zurückkehrt:

// `age` wird nur inkrementiert, wenn `db` und `user` definiert sind.
db?.user?.grow(++age);

Stapeln bedeutet, dass mehr als ein optionaler Verkettungsoperator auf eine Sequenz von Eigenschaftszugriffen angewendet werden kann:

// Eine optionale Verkettung kann von einer weiteren optionalen Verkettung gefolgt werden.
const firstNameLength = db.users?.[42]?.names.first.length;

Dennoch sollte man vorsichtig sein, mehr als einen optionalen Verkettungsoperator in einer einzigen Kette zu verwenden. Wenn ein Wert garantiert nicht null oder undefiniert ist, wird die Verwendung von ?. zum Zugriff auf seine Eigenschaften eher nicht empfohlen. Im obigen Beispiel wird angenommen, dass db immer definiert ist, aber db.users und db.users[42] möglicherweise nicht. Wenn es einen solchen Nutzer in der Datenbank gibt, wird davon ausgegangen, dass names.first.length immer definiert ist.

Optionales Löschen bedeutet, dass der delete-Operator mit einer optionalen Verkettung kombiniert werden kann:

// `db.user` wird nur gelöscht, wenn `db` definiert ist.
delete db?.user;

Weitere Details finden Sie im Semantik-Abschnitt des Vorschlags.

Unterstützung für optionales Verkettung