Als ich JavaScript und TypeScript gelernt und dabei viel Code gelesen habe, den Profis geschrieben hatten, verwirrten mich die seltsamen Zeichenketten, die sich bilden, wenn jemand die Sprache wirklich beherrscht und über Komplexität einfach nicht mehr nachdenkt. Heute, nach ein paar Jahren, schreibe ich selbst so seltsame Dinge wie
style={{ backgroundColor: color, ...(this.props.style || {}) }}
Was zum Henker, dachte ich damals, bedeutet das? (Gut, das da oben ist JSX, nicht JS, doch immer noch weit leichter zu lesen als das hier).
Ich wünschte, mir hätte das 2016 mal jemand sauber erklärt. Na gut, mach‘ ich das halt. Dieser Blogpost wird sozusagen ein nachgeholter „Brief an mein vergangenes Selbst“.
Rund, eckig und geschweift
Am Anfang sieht ja alles noch einfach aus:
n = (3 + 4) * 5;
Ja, das bedeutet wirklich das, was Du denkst, dass es tut. n ist danach 35.
y = f(x)
Ja, auch das bedeutet noch das, was Du erwarten würdest. Eine Funktion „f“ wird aufgerufen, „x“ wird übergeben, und „y“ kommt heraus.
a = [2, 4, 6, 8, 10];
Das ist ein Array mit 5 Zahlen. Dann sollte man erwarten, dass gilt:
8 == a[3]
Auch noch gut (es sei denn, Du triffst einen Profi, der wird dir empfehlen „===“ statt „==“ zu schreiben).
Jetzt das hier:
obj = { a:1, b:2, c:3 }
Mit ein bisschen Nachlesen findest Du, dass dies ein Objekt ist, das drei Eigenschaften hat, die a, b, und c heißen und die mit 1, 2, und 3 initialisiert werden. Also gilt wohl nachher:
3 == obj.c
Funktionen
Jetzt benutzen wir die Zeichen mal etwas kreativer. Was ist das hier:
function quadrat(x) {
return x*x;
}
Ist eine Funktion, die ein Quadrat bildet, oder? Und was ist das hier:
const quadrat = (x) => x*x;
Das ist eine Konstante, die als Wert eine Funktion hat, die das Quadrat einer Zahl bildet (Lambda-Schreibweise).
Beides kann man identisch aufrufen:
y = quadrat(5)
und es kommt in beiden Fällen 25 heraus.
Funktionen auf Arrays
Wie wäre es jetzt mit
[2, 4, 6, 8, 10].map(quadrat)
map ist eine Funktion, die man auf Arrays anwendet, und die als Parameter wieder eine Funktion hat. Das Ergebnis entsteht dadurch, dass die übergebene Funktion auf jedes einzelne Array-Element angewendet und aus den Ergebnissen wieder ein Array gebildet wird. Was kommt heraus? In diesem Falle ist das wohl
[4, 16, 36, 64, 100]
Funktionen auf Objekten
Natürlich kann man auch ein Objekt an eine Funktion übergeben. Wie wäre es mit
function helligkeit1(farbe) {
return 0.2126*farbe.r + 0.7152*farbe.g + 0.0722*farbe.b;
}
Diese Funktion berechnet zu einer Farbe, die aus rot, grün und blau-Anteilen besteht, die Helligkeit dieser Farbe. Was genau ist der Unterschied zwischen der obigen Funktion und dieser hier:
function helligkeit2(r, g, b) {
return 0.2126*r + 0.7152*g + 0.0722*b;
}
Die erste Funktion „helligkeit1“ erwartet einen einzigen Parameter: ein Objekt mit drei Eigenschaften, die „r“, „g“ und „b“ heißen. Die zweite Funktion „helligkeit2“ erwartet hingegen drei Parameter, die einfache Zahlen sind.
Die Aufrufe würden also verschieden aussehen:
h1 = helligkeit1({r: 100, g: 200, b: 50});
h2 = helligkeit2(100, 200, 50);
Destrukturierung
Doch was bedeutet dies hier, was man in Profi-Code sieht?
function helligkeit3({ r, g, b }) {
return 0.2126*r + 0.7152*g + 0.0722*b;
}
Das ist wiederum dasselbe wie Funktion „helligkeit1“: Sie erwartet nur ein Objekt mit drei Eigenschaften. Nur: Der formale Parameter ist anonym, und er wird beim Eintritt in die Funktion quasi zerlegt (destrukturiert) in drei einzelne Variablen r, g, und b.
Doppelbedeutung
Die geschweifte Klammer hat in JavaScript eine Doppelbedeutung. Sie wird für Objekte verwendet und für Beginn und Ende eines Blockes von Anweisungen. Normalerweise ist das ja überhaupt kein Problem, denn man kann ja unterscheiden:
1 const farbe = { r: 100, g: 200, b: 50 };
2
3 function maxBlau10(farbe) {
4 if (farbe.b > 10) {
5 return { r: farbe.r, g: farbe.g, b: 10 };
6 }
7 return farbe;
8 }
Die geschweiften Klammern in Zeilen 1 und 5 kann man gut unterscheiden von denen in den Zeilen 3, 4, 6 und 8, oder? Dann kann man das doch bestimmt auch vereinfachen zu dem hier:
function maxBlau10({r, g, b}) {
return {r, g, b: Math.min(b, 10)};
}
Und dann kann man doch bestimmt noch die tolle Lambda-Schreibweise nutzen, oder? Denn mal los:
const maxBlau10 = ({r, g, b}) => { r, g, b: Math.min(b, 10) };
Dummerweise ergibt das einen Compilerfehler:
const maxBlau10 = ({r, g, b}) => { r, g, b: Math.min(b, 10) };
^ SyntaxError: Unexpected token
Das Problem ist, dass der Compiler die geschweifte Klammer auf als Beginn der Funktion interpretieren möchte und nicht als Beginn eines Objekt-Ausdruckes. Also müssen wir so schreiben:
const maxBlau10 = ({r, g, b}) => ({ r, g, b: Math.min(b, 10) });
Abkürzungen
Seltsam, warum musste ich eigentlich nicht dies hier schreiben:
const maxBlau10 = ({r, g, b}) => ({ r:r, g:g, b: Math.min(b, 10) });
Wie sich herausstellt, bedeutet „{ x }“ in Wirklichkeit „{ x:x }“. Sieht man bei den Profis jeden Tag, doch als Anfänger wundert man sich.
Noch mehr Doppelbedeutung
Auch die eckige Klammer hat eine wunderschöne Doppelbedeutung. Einerseits wird sie zum Indizieren von Arrays genutzt, das kennt man ja. Doch man kann damit auch ein Objekt „indizieren“:
const farbe = { r: 100, g: 200, b: 50 };
console.log(farbe['r']); // => da kommt 100 heraus
Und was zum Teufel ist denn das hier?
const s = '1.2.3';
const obj = { [s]: 'what?' };
Nun ja, „s“ ist ein ganz normaler String. „obj“ hingegen ist ein Objekt, das eine Eigenschaft hat, die „1.2.3“ heißt und den Wert „what?“ hat. Wohlgemerkt, um auf die Eigenschaft zuzugreifen, darf ich später jedoch nicht schreiben „obj.1.2.3“, sondern müsste schreiben „obj[‚1.2.3‘]“.
(Achtung: Mein Blog macht die Apostrophe und die Anführungszeichen in deutscher Form, dadurch sehen die obigen JS-Snippets manchmal seltsam aus).
Nicht wirklich boolesch
Auch schön im Profi-Code ist das ganz selbstverständliche Verknüpfen nicht-boolescher Operanden mit Hilfe von booleschen Operatoren, also mit „&&“ (and) oder mit „||“ (or). Was bedeutet z.B. das hier:
const s2 = s1 || '';
const obj2 = obj1 || {};
Das bedeutet: Sollte s1 undefiniert sein (null oder undefined), dann soll ein leerer String in s2 stehen. Entsprechend: Sollte obj1 undefiniert sein, dann soll in obj ein Objekt ohne Eigenschaften stehen. Das kann man auch wunderschön mit „&&“ machen:
const derVorname = person && person.vorname;
Nur wenn „person“ definiert ist (also eben nicht null oder undefined), dann soll „derVorname“ auch definiert sein.
Nicht verzweifeln
Wenn Du (lieber Matthias vor drei Jahren) jetzt weiter noch viel mehr Profi-Code liest, dann verzweifle nicht, selbst wenn Du so etwas liest:
import x from 'y';
import { x } from 'y';
import * as x from 'y';
Diese drei bedeuten halt jeweils etwas anderes. Nur ein haarfeiner Unterschied, nicht weiter schlimm, führt lediglich dazu, dass Dein Programm nicht compiliert oder erst zur Laufzeit crasht! (Um das zu vermeiden, musst Du lediglich den Export-Mechanismus in den verschiedenen Versionen von JavaScript verstehen, und selbstverständlich webpack mit dem TypeScript- oder Babel-Loader richtig konfigurieren, mehr nicht). 🙂
Auch die Tatsache, dass Du tausende von Zeilen Code in TypeScript schreiben wirst, sollte Dich nicht von Funktionen mit destrukturierten Parametern abhalten. Die gehen zwar in TypeScript nicht sofort, weil TypeScript Typdeklarationen für die destrukturierten Variablen verlangt und Du diese zuerst nicht schaffst, weil der Doppelpunkt in TypeScript-Objektausdrücken eine Doppelbedeutung hat … doch keine Sorge, Du wirst es hinbekommen, weil Du ich bist – und ich habe es schließlich geschafft, also warum Du nicht auch?
Und jetzt ist Dir bestimmt auch dies hier völlig klar, oder? Warte nur, denn Du wirst es in drei Jahren selbst geschrieben haben:
<nav>
{
list && <ul>
{
list.map(item =>
<li className={classNames({
[props.classes.active]: locationMatches()
})}>
{item.name}
</li>
)
}
</ul>
}
</nav>
Dazu musst Du lediglich noch wissen, dass in der Sprache JSX, mit der man Benutzeroberflächen in JavaScript schreibt, die geschweifte Klammer eine dritte Bedeutung bekommt, nämlich den Übergang aus dem HTML-ähnlichen Teil der Sprache in den JavaScript-Teil der Sprache.
Superklar, oder? 🙂 Wir sehen uns in drei Jahren und nehmen einen Kaffee zusammen, versprochen!
Titelfoto
Vielen Dank an den Fotografen Markus Spiske: