Exkurs ES2015+

Das „neue“ JavaScript

ES2015 ist kurz gesagt eine modernisierte, aktuelle Version von JavaScript mit vielen neuen Funktionen und Syntax-Erleichterungen. ES2015 ist der Nachfolger von ECMAScript in der Version 5 (ES5), hieß daher ursprünglich auch einmal ES6 und wird auch in einigen Blogs und Artikeln immer noch so bezeichnet. Stößt du also beim Lesen von Artikeln zu React auf den Begriff ES6, ist damit ES2015 gemeint. Ich schreibe hier meist von ES2015+ und meine damit Änderungen, die seit 2015 in JavaScript eingeflossen sind. Dazu gehören ES2016 (ES7), ES2017 (ES8) und ES2018 (ES9).
Das ES in ES2015 und ES6 steht für ECMAScript. Die ECMA International ist die Organisation, die hinter der Standardisierung der ECMA-262-Spezifikation steht, auf der JavaScript basiert. Seit 2015 werden jährlich neue Versionen der Spezifikation veröffentlicht, die aus historischen Gründen erst eine fortlaufende Versionsnummer beginnend ab Version 1 hatten, dann jedoch für mehr Klarheit die Jahreszahl ihrer Veröffentlichung angenommen haben. So wird ES6 heute offiziell als ES2015 bezeichnet, ES7 als ES2016, usw.
Wer mit React arbeitet, nutzt in vermutlich 99% der Fälle auch Babel als Transpiler, um sein JSX entsprechend in createElement()-Aufrufe zu transpilieren. Doch Babel transpiliert nicht nur JSX in ausführbares JavaScript, sondern hieß ursprünglich mal 6to5 und hat genau das gemacht: mit ES6-Syntax geschriebenes JavaScript in ES5 transpiliert, sodass neuere, zukünftige Features und Syntax-Erweiterungen auch in älteren Browsern ohne Unterstützung für „das neue“ JavaScript genutzt werden konnten.
Auf die meiner Meinung nach wichtigsten und nützlichsten neuen Funktionen und Möglichkeiten in ES2015 und den folgenden Versionen möchte ich in diesem Kapitel eingehen. Dabei werde ich mich auf die neuen Funktionen beschränken, mit denen man bei der Arbeit mit React häufiger zu tun haben wird und die euch Entwicklern das Leben am meisten vereinfachen.
Wenn du bereits Erfahrung mit ES2015 und den nachfolgenden Versionen hast, kannst du dieses Kapitel überspringen!

Variablen-Deklarationen mit let und const

Gab es bisher nur var um in JavaScript eine Variable zu deklarieren, kommen in ES2015 zwei neue Schlüsselwörter dazu, mit denen Variablen deklariert werden können: let und const. Eine Variablendeklaration mit var wird dadurch in fast allen Fällen überflüssig, meist sind let oder const die sauberere Wahl. Doch wo ist der Unterschied?
Anders als var existieren mit let oder const deklarierte Variablen nur innerhalb des Scopes ,in dem sie deklariert wurden! Ein solcher Scope kann eine Funktion sein wie sie bisher auch schon bei var einen neuen Scope erstellt hat, aber auch Schleifen oder gar if-Statements!
Grobe Merkregel: überall dort, wo man eine öffnende geschweifte Klammer findet, wird auch ein neuer Scope geöffnet. Konsequenterweise schließt die schließende Klammer diesen Scope wieder. Dadurch sind Variablen deutlich eingeschränkter und gekapselter, was für gewöhnlich eine gute Sache ist.
Möchte man den Wert einer Variable nochmal überschreiben, beispielsweise in einer Schleife, ist die Variable dafür mit let zu deklarieren. Möchte man die Referenz der Variable unveränderbar halten, sollte const benutzt werden.
Doch Vorsicht: anders als bei anderen Sprachen bedeutet const nicht, dass der komplette Inhalt der Variable konstant bleibt. Bei Objekten oder Arrays kann deren Inhalt auch bei mit const deklarierten Variablen noch verändert werden. Es kann lediglich das Referenzobjekt, auf welche die Variable zeigt, nicht mehr verändert werden.

Der Unterschied zwischen let/const und var

Erst einmal zur Demonstration ein kurzes Beispiel dafür, wie sich die Variablendeklaration von let und const von denen mit var unterscheiden und was es bedeutet, dass erstere nur in dem Scope sichtbar sind, in dem sie definiert wurden:
1
for (var i = 0; i < 10; i++) {}
2
console.log(i);
Copied!
Ausgabe:
10
Nun einmal dasselbe Beispiel mit let
1
for (let j = 0; j < 10; j++) {}
2
console.log(j);
Copied!
Ausgabe:
Uncaught ReferenceError: j is not defined
Während auf die Variable var i, einmal definiert, auch außerhalb der for-Schleife zugegriffen werden kann, existiert die Variable let j nur innerhalb des Scopes, in dem sie definiert wurde. Und das ist in diesem Fall innerhalb der for-Schleife, die einen neuen Scope erzeugt.
Dies ist ein kleiner Baustein, der uns später dabei helfen wird unsere Komponenten gekapselt und ohne ungewünschte Seiteneffekte zu erstellen.

Unterschiede zwischen let und const

Folgender Code ist valide und funktioniert, solange die Variable mittels let (oder var) deklariert wurde:
1
let myNumber = 1234;
2
myNumber = 5678;
3
console.log(myNumber);
Copied!
Ausgabe:
5678
Der gleiche Code nochmal, nun allerdings mit const:
1
const myNumber = 1234;
2
myNumber = 5678;
3
console.log(myNumber);
Copied!
Ausgabe:
Uncaught TypeError: Assignment to constant variable.
Wir versuchen hier also eine durch const deklarierte Variable direkt zu überschreiben und werden dabei vom JavaScript-Interpreter zurecht in die Schranken gewiesen. Doch was, wenn wir stattdessen nur eine Eigenschaft innerhalb eines mittels const deklarierten Objekts verändern wollen?
1
const myObject = {
2
a: 1,
3
};
4
myObject.b = 2;
5
console.log(myObject);
Copied!
Ausgabe:
{a: 1, b: 2}
In diesem Fall gibt es keinerlei Probleme, da wir nicht die Referenz verändern, auf die die myObject Variable verweisen soll, sondern das Objekt, auf das verwiesen wird. Dies funktioniert ebenso mit Arrays, die verändert werden können, solange nicht der Wert der Variable selbst geändert wird!
Erlaubt:
1
const myArray = [];
2
myArray.push(1);
3
myArray.push(2);
4
console.log(myArray);
Copied!
Ausgabe:
[1, 2]
Nicht erlaubt, da wir die Variable direkt überschreiben würden:
1
const myArray = [];
2
myArray = myArray.concat(1, 2);
Copied!
Uncaught TypeError: Assignment to constant variable.
Möchten wir myArray also überschreibbar halten, müssen wir stattdessen let verwenden oder uns damit begnügen, dass zwar der Inhalt des mittels const deklarierten Arrays veränderbar ist, nicht jedoch die Variable selbst.

Arrow Functions

Arrow Functions sind eine weitere deutliche Vereinfachung, die uns ES2015 gebracht hat. Bisher funktionierte eine Funktionsdeklaration so: man schrieb das Keyword function, optional gefolgt von einem Funktionsnamen, Klammern, in der die Funktionsargumente beschrieben wurden, sowie dem Function Body, also dem eigentlichen Inhalt der Funktion:
1
function(arg1, arg2) {}
Copied!
Arrow Functions vereinfachen uns das ungemein, indem sie erst einmal das function-Keyword überflüssig machen:
1
(arg1, arg2) => {};
Copied!
Haben wir zudem nur einen Parameter, sind sogar die Klammern bei den Argumenten optional. Aus unserer Funktion
1
function(arg) {}
Copied!
würde also die folgende Arrow Function werden:
1
(arg) => {};
Copied!
Jap, das ist eine gültige Funktion in ES2015!
Und es wird noch wilder. Soll unsere Funktion lediglich einen Ausdruck als return-Wert zurückgeben, sind auch noch die Klammern optional. Vergleichen wir einmal eine Funktion, die eine Zahl als einziges Argument entgegennimmt, diese verdoppelt und als return-Wert wieder aus der Funktion zurückgibt. Einmal in ES5:
1
function double(number) {
2
return number * 2;
3
}
Copied!
… und als ES2015 Arrow Function:
1
const double = (number) => number * 2;
Copied!
In beiden Fällen liefert uns die eben deklarierte Funktion beim Aufruf von bspw. double(5) als Ergebnis 10 zurück!
Aber es gibt noch einen weiteren gewichtigen Vorteil, der bei der Arbeit mit React sehr nützlich sein wird: Arrow Functions haben keinen eigenen Constructor, können also nicht als Instanz in der Form new MyArrowFunction() erstellt werden, und binden auch kein eigenes this sondern erben this aus ihrem Parent Scope. Insbesondere Letzteres wird noch sehr hilfreich werden.
Auch das klingt fürchterlich kompliziert, lässt sich aber anhand eines einfachen Beispiels ebenfalls recht schnell erklären. Nehmen wir an, wir definieren einen Button, der die aktuelle Zeit in ein div schreiben soll, sobald ich ihn anklicke. Eine typische Funktion in ES5 könnte wie folgt aussehen:
1
function TimeButton() {
2
var button = document.getElementById('btn');
3
var self = this;
4
this.showTime = function() {
5
document.getElementById('time').innerHTML = new Date();
6
};
7
button.addEventListener('click', function() {
8
self.showTime();
9
});
10
}
Copied!
Da die als Event Listener angegebene Funktion keinen Zugriff auf ihren Parent Scope, also den TimeButton hat, speichern wir hier hilfsweise this in der Variable self. Kein unübliches Muster in ES5. Alternativ könnte man auch den Scope der Funktion explizit an this binden und dem Event Listener beibringen, in welchem Scope sein Code ausgeführt werden soll:
1
function TimeButton() {
2
var button = document.getElementById('btn');
3
this.showTime = function() {
4
document.getElementById('time').innerHTML = new Date();
5
};
6
button.addEventListener(
7
'click',
8
function() {
9
this.showTime();
10
}.bind(this)
11
);
12
}
Copied!
Hier spart man sich zumindest die zusätzliche Variable self. Auch das ist möglich, aber nicht besonders elegant.
An dieser Stelle kommt nun die Arrow Function ins Spiel, die, wie eben erwähnt, this aus ihrem Parent Scope erhält, also in diesem Fall aus unserer TimeButton-Instanz:
1
function TimeButton() {
2
var button = document.getElementById('btn');
3
this.showTime = function() {
4
document.getElementById('time').innerHTML = new Date();
5
};
6
button.addEventListener('click', () => {
7
this.showTime();
8
});
9
}
Copied!
Und schon haben wir im Event Listener Zugriff auf this des überliegenden Scopes!
Keine var self = this Akrobatik mehr und auch kein .bind(this). Wir können innerhalb des Event Listeners so arbeiten als befänden wir uns noch immer im TimeButton Scope! Das ist später insbesondere bei der Arbeit mit umfangreichen React-Komponenten mit vielen eigenen Class Properties und Methods hilfreich, da es Verwirrungen vorbeugt und nicht immer wieder einen neuen Scope erzeugt.

Neue Methoden bei Strings, Arrays und Objekten

Mit ES2015 erhielten auch eine ganze Reihe neue statische und Prototype-Methoden Einzug in JavaScript. Auch wenn die meisten davon nicht direkt relevant sind für die Arbeit mit React, erleichtern sie die Arbeit aber gelegentlich doch ungemein, weshalb ich hier ganz kurz auf die wichtigsten eingehen möchte.

String-Methoden

Hat man in der Vergangenheit auf indexOf() oder reguläre Ausdrücke gesetzt, um zu prüfen ob ein String einen bestimmten Wert enthält, mit einem bestimmten Wert anfängt oder aufhört, bekommt der String Datentyp nun seine eigenen Methoden dafür.
Dies sind:
1
string.includes(value);
2
string.startsWith(value);
3
string.endsWith(value);
Copied!
Zurückgegeben wird jeweils ein Boolean, also true oder false. Möchte ich wissen ob mein String Beispielein eis enthält, prüfe ich ganz einfach auf
1
'Beispiel'.includes('eis');
Copied!
Analog verhält es sich mit startsWith:
1
'Beispiel'.startsWith('Bei');
Copied!
… wie auch mit endsWith:
1
'Beispiel'.endsWith('spiel');
Copied!
Die Methode arbeitet dabei case-sensitive, unterscheidet also zwischen Groß- und Kleinschreibung.
Zwei weitere hilfreiche Methoden, die mit ES2015 Einzug in JavaScript erhalten haben, sind String.prototype.padStart() und String.prototype.padEnd(). Diese Methoden könnt ihr nutzen, um einen String auf eine gewisse Länge zu bringen, indem ihr am Anfang (.padStart()) oder am Ende (.padEnd()) Zeichen hinzufügt bis die angegebene Länge erreicht ist. Dabei gibt der erste Parameter die gewünschte Länge an, der optionale zweite Parameter das Zeichen mit dem ihr den String bis zu dieser Stelle auffüllen wollt. Gebt ihr keinen zweiten Parameter an, wird standardmäßig ein Leerzeichen benutzt.
Hilfreich ist das bspw. wenn ihr Zahlen auffüllen wollt, so dass diese immer einheitlich dreistellig sind:
1
'7'.padStart(3, '0'); // 007
2
'72'.padStart(3, '0'); // 072
3
'132'.padStart(3, '0'); // 132
Copied!
String.prototype.padEnd() funktioniert nach dem gleichen Muster, mit dem Unterschied, dass es euren String am Ende auffüllt, nicht am Anfang.

Arrays

Bei den Array-Methoden gibt es sowohl neue statische als auch Methoden auf dem Array-Prototype. Was bedeutet dies? Prototype-Methoden arbeiten „mit dem Array“ als solches, also mit einer bestehenden Array-Instanz, statische Methoden sind im weiteren Sinne Helper-Methoden, die gewisse Dinge tun, die „mit Arrays zu tun haben“.

Statische Array-Methoden

Fangen wir mit den statischen Methoden an:
1
Array.of(3); // [3]
2
Array.of(1, 2, 3); // [1, 2 ,3]
3
Array.from('Example'); // ['E', 'x', 'a', 'm', 'p', 'l', 'e']
Copied!
Array.of() erstellt eine neue Array-Instanz aus einer beliebigen Anzahl an Parametern, unabhängig von deren Typen. Array.from() erstellt ebenfalls eine Array-Instanz, allerdings aus einem „array-ähnlichen“ iterierbaren Objekt. Das wohl griffigste Beispiel für ein solches Objekt ist eine HTMLCollection oder eine NodeList. Solche erhält man bspw. bei der Verwendung von DOM-Methoden wie getElementsByClassName() oder dem moderneren querySelectorAll(). Diese besitzen selbst keine Methoden wie .map() oder .filter(). Möchte man über eine solche also iterieren, muss man sie erst einmal in einen Array konvertieren. Dies geht mit ES2015 nun ganz einfach durch die Verwendung von Array.from().
1
const links = Array.from(document.querySelectorAll('a'));
2
Array.isArray(links); // true
Copied!

Methoden auf dem Array-Prototypen

Die Methoden auf dem Array-Prototypen können direkt auf eine Array-Instanz angewendet werden. Die gängigsten während der Arbeit mit React und insbesondere später mit Redux sind:
1
Array.find(func);
2
Array.findIndex(func);
3
Array.includes(value);
Copied!
Die Array.find()-Methode dient, wie der Name es erahnen lässt, dazu, das erste Element eines Arrays zu finden, das bestimmte Kriterien erfüllt, die mittels der als ersten Parameter übergebenen Funktion geprüft werden.
1
const numbers = [1, 2, 5, 9, 13, 24, 27, 39, 50];
2
const biggerThan10 = numbers.find((number) => number > 10); // 13
3
4
const users = [
5
{ id: 1, name: 'Manuel' },
6
{ id: 2, name: 'Bianca' },
7
{ id: 3, name: 'Brian' },
8
];
9
10
const userWithId2 = users.find((user) => user.id === 2);
11
// { id: 2, name: 'Bianca'}
Copied!
Die Array.findIndex()-Methode folgt der gleichen Signatur, liefert aber anders als die Array.find()-Methode nicht das gefundene Element selbst zurück, sondern nur dessen Index im Array. In den obigen Beispielen wären dies also 4 im ersten Beispiel, sowie 1 im zweiten.
Die in ES2016 neu dazu gekommene Methode Array.includes() prüft, ob ein Wert innerhalb eines Array existiert und gibt uns endlich einen Boolean zurück. Wer selbiges in der Vergangenheit mal mit Array.indexOf() realisiert hat, wird sich erinnern wie umständlich es war. Nun also ein simples Array.includes():
1
[1, 2, 3, 4, 5].includes(4); // true
2
[1, 2, 3, 4, 5].includes(6); // false
Copied!
Aufgepasst: die Methode ist case-sensitive. ['a', 'b'].includes('A') gibt also false zurück.

Objekte

Statische Objekt-Methoden

Natürlich haben auch Objekte eine Reihe neuer Methoden und anderer schöner Möglichkeiten spendiert bekommen. Die wichtigsten im Überblick:
1
Object.assign(target, source[, source[,...]]);
2
Object.entries(Object)
3
Object.keys(Object)
4
Object.values(Object)
5
Object.freeze(Object)
Copied!
Wieder der Reihe nach. Die wohl nützlichste ist aus meiner Sicht Object.assign(). Damit ist es möglich, die Eigenschaften eines Objekts oder auch mehrerer Objekte zu einem bestehenden Objekt hinzuzufügen (sozusagen ein Merge). Die Methode gibt dabei das Ergebnis als Objekt zurück. Allerdings findet dabei auch eine Mutation des Ziel-Objekts statt, weswegen die Methode mit Bedacht benutzt werden sollte. Beispiele sagen mehr als Worte, bitteschön:
1
const user = { id: 1, name: 'Manuel' };
2
const modifiedUser = Object.assign(user, { role: 'Admin' });
3
console.log(user);
4
// -> { id: 1, name: 'Manuel', role: 'Admin' }
5
console.log(modifiedUser);
6
// -> { id: 1, name: 'Manuel', role: 'Admin' }
7
console.log(user === modifiedUser);
8
// -> true
Copied!
Hier fügen wir also die Eigenschaft role aus dem Objekt im zweiten Parameter der Object.assign()-Methode zum bestehenden Ziel-Objekt hinzu.
Da React dem Prinzip von Pure Functions folgt, das sind Funktionen die in sich geschlossen sind und ihre Eingabeparameter nicht modifizieren, sollten derartige Mutationen möglichst vermieden werden. Dies können wir umgehen, indem wir als ersten Parameter einfach ein Object-Literal übergeben:
1
const user = { id: 1, name: 'Manuel' };
2
const modifiedUser = Object.assign({}, user, { role: 'Admin' });
3
console.log(user);
4
// -> { id: 1, name: 'Manuel' }
5
console.log(modifiedUser);
6
// -> { id: 1, name: 'Manuel', role: 'Admin' }
7
console.log(user === modifiedUser);
8
// -> false
Copied!
Durch die Verwendung eines neu erstellten Objekts als Ziel-Objekt bekommen wir hier eben auch als Rückgabewert ein anderes Objekt als im ersten Beispiel. In einigen Fällen kann es gewünscht sein, das Ziel-Objekt zu mutieren statt ein neues Objekt zu erstellen, während der Arbeit mit React ist dies jedoch in den deutlich überwiegenden Fällen nicht so.
Die Methode verarbeitet dabei auch beliebig viele Objekte als Parameter. Gibt es gleichnamige Eigenschaften in einem Objekt, haben spätere Eigenschaften Vorrang:
1
const user = { id: 1, name: 'Manuel' };
2
const modifiedUser = Object.assign(
3
{},
4
user,
5
{ role: 'Admin' },
6
{ name: 'Nicht Manuel', job: 'Developer' }
7
);
8
console.log(modifiedUser);
9
// -> { id: 1, name: 'Nicht Manuel', role: 'Admin', job: 'Developer' }
Copied!
Die drei statischen Objekt-Methoden Object.entries(), Object.keys() und Object.values() funktionieren im Grunde sehr ähnlich, sie liefern zu einem übergebenen Objekt die Eigenschaften (keys), die Werte (values) oder die Einträge (entries) als Array zurück, wobei die Entries ein verschachteltes Array sind in der Form[[key, value], [key2, values2], …].
Angewendet auf unser obiges Beispiel hat dies also folgende Return-Values zum Ergebnis:
1
Object.keys({ id: 1, name: 'Manuel' });
2
// -> ['id', 'name']
3
Object.values({ id: 1, name: 'Manuel' });
4
// -> [1, 'Manuel']
5
Object.entries({ id: 1, name: 'Manuel' });
6
// -> [['id', 1], ['name', 'Manuel']]
Copied!
Zuletzt schauen wir uns Object.freeze() an. Auch diese Methode ist ziemlich selbsterklärend und tut genau, was der Name vermuten lässt: sie friert ein Objekt ein, untersagt es dem Entwickler also, neue Eigenschaften hinzuzufügen und alte Eigenschaften zu löschen oder auch nur zu verändern. Auch dies ist im Umgang mit den Objekten, die in React in den meisten Fällen unveränderlich sind (oder zumindest sein sollten), unglaublich praktisch.
1
const user = Object.freeze({ id: 1, name: 'Manuel' });
2
user.id = 2;
3
delete user.name;
4
user.role = 'Admin';
5
console.log(user);
6
// -> { id: 1, name: 'Manuel' }
Copied!
Ein mittels Object.freeze() erstelltes Objekt bietet auch guten Schutz vor versehentlicher Mutation mittels der oben beschriebenen, ebenfalls neuen Object.assign()-Methode. Wird versucht, ein mittels Object.freeze() erstelltes Objekt per Object.assign() zu modifizieren, führt dies unweigerlich zu seinem TypeError.

Syntax-Erweiterungen und Vereinfachungen

Die letzten Änderungen an der Funktionsweise von Objekten sind keine Methode, sondern Syntax-Erweiterungen.
Die erste sind die Computed Properties (also etwa berechnete Eigenschaften). Dahinter verbirgt sich die Möglichkeit, Ausdrücke (bzw. deren Werte) als Objekt-Eigenschaften zu verwenden. Wollte man bspw. früher eine Eigenschaft in einem Objekt setzen, lief das meist so, dass man das Objekt erstellte (bspw. als Object-Literal {} oder per Object.create()), dieses einer Variablen zuwies und anschließend die neue Eigenschaft zum Objekt hinzufügte:
1
const nationality = 'german';
2
const user = {
3
name: 'Manuel',
4
};
5
user[nationality] = true;
6
console.log(user);
7
// -> { name: 'Manuel', german: true };
Copied!
ES2015 erlaubt uns nun, Ausdrücke direkt als Objekt-Eigenschaft zu nutzen, indem wir sie in eckige Klammern [] setzen. Dadurch sparen wir uns den Umweg, nachträglich noch Eigenschaften zum bereits erstellten Objekt hinzuzufügen:
1
const nationality = 'german';
2
const user = {
3
name: 'Manuel',
4
};
5
console.log(user);
6
// -> { name: 'Manuel', german: true };
Copied!
Das Beispiel ist aus Gründen der einfacheren Verständlichkeit ein simples, doch die Verwendungsmöglichkeiten werden später mitunter noch deutlich komplexer und schaffen uns viele Möglichkeiten um sauberen und gut verständlichen Code zu schreiben, insbesondere wenn es um JSX geht.
Die letzte nennenswerte Neuerung bei Objekten sind die sogenannten Shorthand Property Names. Diese ersparen uns eine Menge unnötige Schreibarbeit. Nicht erst seit React kennt man es, dass man auf Code wie den folgenden stößt:
1
const name = 'Manuel';
2
const job = 'Developer';
3
const role = 'Author';
4
5
const user = {
6
name: name,
7
job: job,
8
role: role,
9
};
Copied!
Ziemlich viele unnötige Dopplungen wenn man sich das mal genau anschaut, oder? Genau diese nimmt uns die Shorthand Property Name Syntax in ES2015 endlich ab. Und so reicht es, nur noch die Variable zu schreiben, wenn diese den Namen der entsprechenden Objekt-Eigenschaft trägt. Im obigen Fall also:
1
const name = 'Manuel';
2
const job = 'Developer';
3
const role = 'Author';
4
5
const user = {
6
name,
7
job,
8
role,
9
};
Copied!
Jep. Seit ES2015 führen beide Schreibweisen tatsächlich zum identischen Objekt! Dabei kann die Shorthand Syntax auch problemlos mit der herkömmlichen Syntax kombiniert werden:
1
const name = 'Manuel';
2
const job = 'Developer';
3
4
const user = {
5
name,
6
job,
7
role: 'Author',
8
};
Copied!

Classes

Mit ES2015 fanden auch Klassen Einzug in JavaScript. Klassen kennt man eher aus objektorientierten Sprachen wie Java, in JavaScript gab es sie so explizit bisher allerdings noch nicht. Zwar war es auch schon vorher durch die Verwendung von Funktionsinstanzen möglich, objektorientiert zu arbeiten und durch die prototype-Eigenschaft einer Funktion eigene Methoden und Eigenschaften zu definieren, dies war verglichen mit echten objektorientierten Sprachen jedoch sehr mühsam und schreiblastig.
Dies ändert sich mit ES2015, wo es nun erstmals auch Klassen gibt, die mittels class-Keyword definiert werden. Das ist für uns insofern interessant, als React, obwohl es viele Prinzipien der funktionalen Programmierung (Functional Programming) verfolgt, gleichzeitig auch in einem wesentlichen Punkt auf ES2015-Klassen setzt;- nämlich bei der Erstellung von Komponenten, in diesem Fall speziell von Class Components. Auch vor der Einführung von ES2015 Klassen war es natürlich möglich, Komponenten in React zu definieren, dazu gab es eine eigene createClass()-Methode. Diese ist aber mittlerweile nicht mehr Teil des React Cores und sollte möglichst auch nicht mehr verwendet werden.
Eine Klasse besteht aus einem Namen, kann (optional) einen Constructor haben, der bei der Erstellung einer Klassen-Instanz aufgerufen wird, und beliebig viele Klassen-Methoden besitzen.
1
class Customer {
2
constructor(firstName, lastName) {
3
this.firstName = firstName;
4
this.lastName = lastName;
5
}
6
7
getFullName() {
8
return this.firstName + ' ' + this.lastName;
9
}
10
}
11
12
const firstCustomer = new Customer('Max', 'Mustermann');
13
console.log(firstCustomer.getFullName());
Copied!
Ausgabe:
Max Mustermann
Auch das Erweitern bestehender Klassen mittels extends ist dabei möglich:
1
class Customer extends Person {}
Copied!
Oder eben:
1
class MyComponent extends React.Component {}
Copied!
Auch eine super()-Funktion kennt die neu eingeführte ES2015-Klasse, um damit den Constructor ihrer Elternklasse aufzurufen. Im Falle von React ist dies immer notwendig, wenn ich in meiner eigenen Klasse eine constructor-Methode definiere. Diese muss dann super() aufrufen und ihre props an den Constructor der React.Component-Klasse weiterzugeben:
1
class MyComponent extends React.Component {
2
constructor(props) {
3
super(props);
4
}
5
}
Copied!
Tätet ihr das nicht, wäre this.props innerhalb eurer Komponente undefined und ihr könntet nicht auf die Props eurer Komponente zugreifen. Grundsätzlich sollte die Verwendung eines Constructors aber in den allermeisten Fällen nicht nötig sein, denn React stellt eigene sog. Lifecycle-Methoden bereit, die der Verwendung des Constructors vorzuziehen sind.

Rest und Spread Operators und Destrukturierung

Eine weitere deutliche Vereinfachung ist die Einführung der sog. Rest und Spread Operators für Objekte und Arrays. Streng genommen handelt es sich bei deren Verwendung in Kombination mit Objekten noch gar nicht um ES2015 Features, da diese sich noch in der Diskussion befinden und noch gar nicht endgültig in die ECMAScript-Spezifikation aufgenommen wurden. Dies ändert sich erst mit ES2018. Eingeführt wurden Rest und Spread in ES2015 erstmals für Arrays. Durch die Verwendung von Babel ist aber auch heute bereits die Nutzung mit Objekten möglich und für gewöhnlich wird davon in React-basierten Projekten auch rege Gebrauch gemacht.
Aber was ist das jetzt überhaupt? Fangen wir mit dem Spread Operator an.

Spread Operator

Der Spread Operator sorgt dafür, Werte sozusagen „auszupacken“. Wollte man in ES5 mehrere Argumente aus einem Array an eine Funktion übergeben, geschah das bisher meist über Function.prototype.apply():
1
function sumAll(number1, number2, number3) {
2
return number1 + number2 + number3;
3
}
4
var myArray = [1, 2, 3];
5
sumAll.apply(null, myArray);
Copied!
Ausgabe:
6
Mit dem Spread Operator, der aus drei Punkten (...) besteht, kann ich diese Argumente nun auspacken oder eben „spreaden“:
1
function sumAll(number1, number2, number3) {
2
return number1 + number2 + number3;
3
}
4
var myArray = [1, 2, 3];
5
sumAll(...myArray);
Copied!
Ausgabe:
6
Ich muss also nicht mehr den Umweg über apply() gehen. Aber nicht nur bei Funktionsargumenten ist das hilfreich. Ich kann ihn auch nutzen, um bspw. auf einfache Art und Weise zwei Arrays zu einem einzigen zu kombinieren:
1
const greenFruits = ['kiwi', 'apple', 'pear'];
2
const redFruits = ['strawberry', 'cherry', 'raspberry'];
3
const allFruits = [...greenFruits, ...redFruits];
Copied!
Ergebnis:
['kiwi', 'apple', 'pear', 'strawberry', 'cherry', 'raspberry']
Dabei wird ein neues Array erstellt, welches alle Werte sowohl aus dem greenFruits als auch aus dem redFruits Array enthält. Doch nicht nur das: dabei wird auch ein gänzlich neues Array erstellt und nicht bloß eine Referenz der beiden alten. Dies wird im weiteren Verlauf, wenn wir an die readonly-Anforderung unserer Props noch sehr nützlich sein. Und so kann man den Spread Operator auch verwenden um eine einfache Kopie eines Arrays zu erstellen:
1
const users = ['Manuel', 'Chris', 'Ben'];
2
const selectedUsers = [...users];
Copied!
selectedUsers ist in diesem Fall eine Kopie unseres users Arrays mit all seinen Werten. Verändern wir nun das Users Array, hat dies auf unser selectedUsers Array keinerlei Auswirkungen.
Bei Objekten verhält sich der Spread Operator sehr ähnlich. Hier werden statt der einzelnen Werte alle Eigenschaften eines Objekts die „enumerable“ (aufzählbar) sind, also ganz grob gesagt bei der Verwendung in einer for(… in …) Schleife angezeigt werden würden.
Hier eignet sich der Spread Operator hervorragend um neue Objekte zu erstellen:
1
const globalSettings = { language: 'en-US', timezone: 'Berlin/Germany' };
2
const userSettings = { mutedUsers: ['Manuel'] };
3
const allSettings = { ...globalSettings, ...userSettings };
4
console.log(allSettings);
Copied!
Ausgabe:
1
{
2
language: 'en-US',
3
timezone: 'Berlin/Germany',
4
mutedUsers: ['Manuel'],
5
}
Copied!
Die Eigenschaften beider Objekte finden sich dabei im neu erstellten, kombinierten allSettings-Objekt wieder. Dabei ist der Spread Operator hier nicht auf zwei Objekte beschränkt, sondern kann beliebige weitere Objekte zu einem einzelnen neuen Objekt kombinieren. Auch die Kombination mit einzelnen Eigenschaften ist möglich:
1
const settings = {
2
...userSettings,
3
showWarnings: true,
4
};
Copied!
Befinden sich in beiden Objekten Eigenschaften mit dem gleichen Namen, hat das letztgenannte Objekt Vorrang:
1
const globalSettings = { language: 'en-US', timezone: 'Berlin/Germany' };
2
const userSettings = { language: 'de-DE' };
3
const allSettings = { ...globalSettings, ...userSettings };
4
console.log(allSettings);
Copied!
Ausgabe:
1
{
2
language: 'de-DE',
3
timezone: 'Berlin/Germany',
4
}
Copied!
Das zuletzt genannte userSettings-Objekt überschreibt hier die gleichnamige Eigenschaft language, die sich auch im globalSettings-Objekt befindet. Der Spread Operator funktioniert hier ganz ähnlich wie die in ES2015 neu eingeführte Objekt-Methode Object.assign(). Auch diese wird in Anwendungen, die auf ES2015+ basieren, gelegentlich genutzt.
Allerdings gibt es hier den nennenswerten Unterschied, dass sie ein bestehendes Objekt mutiert und nicht per se ein neues Objekt generiert, wie das die Object-Spread-Variante tut. Und Mutation ist bezogen auf React-Komponenten und ihre Props eben das, was wir ja nicht wollen. Dennoch der Vollständigkeit halber ein kurzes Beispiel.

Objekte kombinieren mittels Object.assign()

Object.assign() nimmt beliebig viele Objekte entgegen und kombiniert diese zu einem einzigen Objekt:
1
const a = { a: 1 };
2
const b = { b: 2 };
3
const c = { c: 3 };
4
console.log(Object.assign(a, b, c));
Copied!
Ausgabe:
1
{a: 1, b: 2, c: 3}
Copied!
Die Funktion gibt uns also ein neues Objekt zurück, in dem alle 3 übergebenen Objekte zu einem einzigen kombiniert wurden. Aber ist das wirklich ein neues Objekt? Nein! Lassen wir uns doch anschließend mal a, b und c in der Console ausgeben:
1
console.log(a);
2
console.log(b);
3
console.log(c);
Copied!
Ausgabe:
1
{a: 1, b: 2, c: 3}
2
{b: 2}
3
{c: 3}
Copied!
Wir stellen also fest: Object.assign() hat uns nicht wirklich ein komplett neues Objekt aus den 3 übergebenen Objekten erstellt sondern lediglich die Eigenschaften des zweiten und dritten Objekts zum ersten übergebenen Objekt hinzugefügt. Und das ist, in Bezug auf Pure Functions und Immutable Objects, äußerst schlecht und in jedem Fall zu vermeiden!
Hier gibt es aber einen einfachen Trick um Objekte mittels Object.assign() zu kombinieren und dabei gleichzeitig ein neues Objekt zu erstellen. Dazu übergebt ihr der Funktion als erstes Argument ein leeres Object-Literal {}:
1
Object.assign({}, a, b, c);
Copied!
… und schon werden dem neu erstellten {} Objekt die Eigenschaften unserer Objekte a, b und c übertragen, die bestehenden Objekte a, b und c bleiben dabei unangetastet!

Destructuring Assignment / destrukturierende Zuweisung

Bevor ich zum Rest Operator komme, der logisch sehr eng mit dem Spread Operator in Verbindung steht und meist mit diesem in einem Atemzug genannt wird, möchte ich auf das Destructuring Assignment (kurz: Destructuring) oder eben die destrukturierende Zuweisung (kurz: Destrukturierung), wie der schöne Begriff auf Deutsch heißt, eingehen. Ich werde hier wie so oft beim englischen Begriff bleiben, da ich den deutschen Begriff selbst in deutschsprachigen Texten selten bisher gelesen habe.
Mittels Destructuring ist es möglich, einzelne Elemente aus Objekten oder Arrays zu extrahieren und Variablen zuzuweisen. Eine weitere deutliche Syntax-Erweiterung, die uns ES2015 hier beschert hat.

Destructuring von Arrays

Stellen wir uns vor, wir möchten aus einem geordneten Array mit den Olympia-Teilnehmern im 100m-Lauf jeweils den Gewinner der Gold-, Silber- und Bronzemedaille in eine eigene Variable schreiben. Auf herkömmlichem (also ES5) Weg funktionierte das bisher folgendermaßen:
1
const athletes = [
2
'Usain Bolt',
3
'Andre De Grasse ',
4
'Christophe Lemaitre ',
5
'Adam Gemili',
6
'Churandy Martina',
7
'LaShawn Merritt',
8
'Alonso Edward',
9
'Ramil Guliyev',
10
];
11
12
const gold = athletes[0];
13
const silver = athletes[1];
14
const bronze = athletes[2];
Copied!
Dank Destructuring können wir dies auf ein einzelnes Statement verkürzen:
1
const [gold, silver, bronze] = athletes;
Copied!
Die Werte der Array-Elemente 0, 1 und 2 befinden sich dann der Reihe nach in den Variablen gold, silver und bronze, wie auch im ersten Beispiel, jedoch mit deutlich weniger Schreibarbeit!
Dies funktioniert überall wo wir mit einem Array auf der rechten Seite (also hinter dem = Zeichen) einer Zuweisung arbeiten, also auch wenn wir diesen als return-Wert aus einer Funktion erhalten:
1
const getAllAthletes = () => {
2
return [
3
'Usain Bolt',
4
'Andre De Grasse ',
5
'Christophe Lemaitre ',
6
'Adam Gemili',
7
'Churandy Martina',
8
'LaShawn Merritt',
9
'Alonso Edward',
10
'Ramil Guliyev',
11
];
12
};
13
14
const [gold, silver, bronze] = getAllAthletes();
Copied!
Die Arrow Function gibt uns hier ein Array mit allen Athleten zurück, dementsprechend können wir hier direkt beim Aufruf bereits das Destructuring nutzen und müssen den return-Wert bspw. nicht erst eigens in einer temporären Variable speichern.
Möchten wir auf diese Weise einzelne Elemente des Arrays auslassen, ist das buchstäblich durch Auslassen des entsprechenden Wertes möglich:
1
const [, silver, bronze] = athletes;
Copied!
Hier würden wir auf das Deklarieren einer Variable gold verzichten und nur die Gewinner der Silber- und Bronze-Medaille in entsprechenden Variablen speichern.
Doch nicht nur bei der offensichtlichen Variablenzuweisung mittels let oder const kann Array Destructuring verwendet werden. Auch bei weniger offensichtlichen Zuweisungen, wie bei der Übergabe von Funktionsargumenten in Form eines Arrays.
1
const logWinners = (athletes) => {
2
const gold = athletes[0];
3
const silver = athletes[1];
4
const bronze = athletes[2];
5
console.log('Winners of Gold, Silver and Bronze are', gold, silver, bronze);
6
};
Copied!
Das geht einfacher:
1
const logWinners = ([gold, silver, bronze]) => {
2
console.log('Winners of Gold, Silver and Bronze are', gold, silver, bronze);
3
};
Copied!
Hier reichen wir das Array in unsere logWinners()-Funktion herein und statt für jeden Medaillengewinner eine Variable pro Zeile zu deklarieren, nutzen wir auch in diesem Fall ganz einfach wieder die Destructuring-Methode von oben.

Destructuring von Objekten

Das Prinzip des Destructurings ist nicht allein auf Arrays beschränkt. Auch Objekte können auf diese Art Variablen zugeordnet werden, die standardmäßig mit dem Namen einer Eigenschaft übereinstimmen.
Die Schreibweise ist dabei ähnlich der bei Arrays, mit dem Unterschied, dass wir die Werte nicht anhand ihrer Position im Objekt zuweisen sondern anhand ihres Eigenschafts-Namens. Außerdem setzen wir die Zuweisung in die für Objekte typischen geschweiften Klammern statt in eckige Klammern.
1
const user = {
2
firstName: 'Manuel',
3
lastName: 'Bieh',
4
job: 'JavaScript Developer',
5
image: 'manuel.jpg',
6
};
7
const { firstName } = user;
Copied!
Die Variable firstName enthält nun den Wert aus user.firstName!
Das Object Destructuring ist eins der wohl meist verwendeten Features, das man in den meisten React-Komponenten findet. Es erlaubt uns, einzelne Props in Variablen zu schreiben und an entsprechenden Stellen im JSX auf unkomplizierte Weise zu verwenden.
Nehmen wir an dieser Stelle einmal die folgende Stateless Functional Component als Beispiel:
1
const UserPersona = (props) => {
2
return (
3
<div>
4
<img src={props.image} alt="User Image" />
5
{props.firstName} {props.lastName}
6
<br />
7
<strong>{props.job}</strong>
8
</div>
9
);
10
};
Copied!
Die ständige Wiederholung von props vor jeder Eigenschaft erschwert die Lesbarkeit der Komponente unnötig. Hier können wir uns Object Destructuring zu Nutze machen um einmalig eine Variable für jede Eigenschaft unserer props zu deklarieren.
1
const UserPersona = (props) => {
2
const { firstName, lastName, image, job } = props;
3
return (
4
<div>
5
<img src={image} alt="User Image" />
6
{firstName} {lastName}
7
<br />
8
<strong>{job}</strong>
9
</div>
10
);
11
};
Copied!
Damit wirkt unsere Komponente schon deutlich aufgeräumter und lesbarer. Doch es geht noch einfacher. Wie auch bei Arrays ist es möglich, Objekte direkt bei der Übergabe als Funktionsargument zu destrukturieren. Statt des props-Arguments nutzen wir dafür das Destructuring Assignment direkt:
1
const UserPersona = ({ firstName, lastName, image, job }) => (
2
<div>
3
<img src={image} alt="User Image" />
4
{firstName} {lastName}
5
<br />
6
<strong>{job}</strong>
7
</div>
8
);
Copied!
Als Bonus nutzen wir hier sogar die direkte Rückgabe aus der Funktion ohne geschweifte Klammern und explizites return Statement aus dem Kapitel über Arrow Functions, da wir ja nun mit unserem auf 5 Zeilen reduzierten JSX einen Ausdruck haben, den wir direkt aus der Arrow Function zurückgeben können.
Während der Arbeit mit React trifft man ständig auf derartige Syntax in SFCs, auch bei Class Components findet man sehr häufig zu Beginn der render()-Methode einer Komponente ein ähnliches Destructuring Assignment in der Form:
1
render() {
2
const { firstName, lastName, image, job } = this.props;
3
// weiterer Code
4
}
Copied!
Es ist euch natürlich hinterher freigestellt, ob ihr das so macht oder innerhalb der Funktion einfach weiterhin direkt auf this.props.firstName zugreift. Dieses Muster hat sich aber mittlerweile zu einer Art Best Practice entwickelt und wurde in den meisten Projekten so gehandhabt, da es den Code am Ende in den meisten Fällen lesbarer werden lässt und auch leichter verständlich ist.
Umbenennung von Eigenschaften beim Destructuring
Manchmal ist es notwendig, Eigenschaften umzubenennen; entweder weil es bereits Variablen mit dem selben Namen gibt oder die Eigenschaft kein gültiger Variablenname wäre. All das ist denkbar und möglich. Und ES2015 bietet uns auch eine Lösung dafür.
1
const passenger = {
2
name: 'Manuel Bieh',
3
class: 'economy',
4
};
Copied!
Das obige passenger Objekt enthält die Eigenschaft class, die als Name für eine Eigenschaft gültig ist, als Name für eine Variable jedoch nicht. Ein direktes Destructuring wäre hier also nicht möglich und würde zu einem Fehler führen:
1
const { name, class } = passenger;
Copied!
Uncaught SyntaxError: Unexpected token }
Um hier den Namen der Variable umzubenennen, muss der Eigenschaft der neue Name getrennt durch einen Doppelpunkt : übergeben werden. Ein korrektes Destructuring Assignment wäre also in diesem Fall in etwa folgendes:
1
const { name, class: ticketClass } = passenger;
Copied!
Hier schreiben wir den Wert der class Eigenschaft in eine Variable ticketClass, was anders als class ein gültiger Name für eine Variable ist. Der Name des Passagiers landet dabei ganz gewöhnlich in einer Variable mit dem Namen name.
Standardwerte beim Destructuring vergeben
Auch die Vergabe von Standardwerten beim Destructuring ist möglich! Ist im Objekt, das destrukturiert wird, eine Eigenschaft nicht definiert, wird stattdessen der Default verwendet. Ähnlich wie bei der Umbenennung wird dabei die jeweilige Eigenschaft wie gehabt vorangestellt, jedoch gefolgt von einem Gleich-Zeichen und dem entsprechenden Standardwert:
1
const { name = 'Unknown passenger' } = passenger;
Copied!
Der Wert von name wäre nun Unknown passenger, wenn im passenger-Objekt keine Eigenschaft name existiert oder deren Wert undefined ist. Existiert diese hingegen, ist aber leer (also bspw. ein leerer String oder null) wird der Standardwert nicht an dessen Stelle verwendet!
Kombination von Umbenennung und Standardwerten
Jetzt wird es verrückt, denn auch das ist möglich. Die Umbenennung von Eigenschaften in Variablennamen bei gleichzeitiger Verwendung von Standardwerten. Die Syntax dafür ist allerdings etwas, wo man bei der ersten Begegnung sicherlich einen Moment länger hinschauen muss. Wir bleiben wieder bei unserem passenger-Objekt aus den Beispielen zuvor. Anforderung ist nun die Zuweisung der name Eigenschaft zu einer Variable mit dem Namen passengerName, die den Wert Unknown Passenger tragen soll, wenn kein Name vorhanden ist. Außerdem möchten wir weiterhin class in ticketClass umbenennen und den Passagier gleichzeitig in Economy einordnen, sollte es im entsprechenden Objekt keine class Eigenschaft geben.
1
const {
2
name: passengerName = 'Unknown passenger',
3
class: ticketClass = 'economy',
4
} = passenger;
Copied!
Hier besitzen die Variablen passengerName und ticketClass die Werte Unknown passenger und economy, wenn diese nicht im destrukturierten Objekt existieren. Doch Vorsicht: Das Objekt selbst darf nicht null sein, andernfalls bekommen wir vom JavaScript Interpreter einen unschönen Fehler geworfen:
1
const {
2
name: passengerName = 'Unknown passenger',
3
class: ticketClass = 'economy',
4
} = null;
Copied!
Uncaught TypeError: Cannot destructure property `name` of 'undefined' or 'null'.
Hier gibt es einen unsauberen aber doch oft praktischen Trick um sicherzustellen, dass das Objekt selbst nicht null oder undefined ist. Dazu machen wir uns den Logical OR Operator zu nutze und verwenden ein leeres Objekt als Fallback, falls unser eigentliches Objekt eben null oder undefined ist:
1
const {
2
name: passengerName = 'Unknown passenger',
3
class: ticketClass = 'economy',
4
} = passenger || {};
Copied!
Mit dem angehängten || {} sagen wir: ist das passenger Objekt falsy, nutze stattdessen ein leeres Objekt. Die vermutlich „sauberere“ Variante wäre es vorab zu prüfen ob passenger auch wirklich ein Objekt ist und das Destructuring nur dann auszuführen. Die Variante mit dem Logical OR Fallback ist allerdings schön kurz und dürfte in vielen Fällen ausreichen.
Destructuring kann übrigens auch problemlos mit dem Spread Operator zusammen verwendet werden:
1
const globalSettings = { language: 'en-US' };
2
const userSettings = { timezone: 'Berlin/Germany' };
3
const { language, timezone } = { ...globalSettings, ...userSettings };
Copied!
Hier wird zuerst der Spread Operator aufgelöst, also ein Objekt mit allen Eigenschaften aus den beiden Objekten globalSettings und userSettings erzeugt und anschließend per Destructuring Assignment entsprechenden Variablen zugewiesen.

Rest Operator

Der Rest Operator dient dazu, sich um die verbliebenen Elemente aus einem Destructuring und in Funktionsargumenten zu kümmern. Daher der Name: der Operator kümmert sich um den verbliebenen „Rest“. Wie auch schon der Spread Operator wird auch der Rest Operator mit drei Punkten eingeleitet, jedoch nicht auf der rechten Seite einer Zuweisung, sondern auf der linken. Anders als beim Spread Operator kann es pro Ausdruck jedoch nur jeweils einen Rest Operator geben!
Schauen wir uns zuerst einmal den Rest Operator bei Funktionsargumenten an. Sagen wir, wir möchten nun eine Funktion schreiben, die beliebig viele Argumente empfängt. Hier möchten wir natürlich auch auf all diese Argumente zugreifen können, egal ob das 2, 5 oder 25 sind. In ES5 Standardfunktionen gibt es das Keyword arguments, mittels dessen auf ein Array aller übergebenen Funktionsargumente zugegriffen werden kann innerhalb der Funktion:
1
function Example() {
2
console.log(arguments);
3
}
4
Example(1, 2, 3, 4, 5);
Copied!
Ausgabe:
Arguments(5) [1, 2, 3, 4, 5, callee: ƒ]
Arrow Functions bieten diese Möglichkeit nicht mehr und werfen stattdessen einen Fehler:
1
const Example = () => {
2
console.log(arguments);
3
};
4
Example(1, 2, 3, 4, 5);
Copied!
Ausgabe:
Uncaught ReferenceError: arguments is not defined
Hier kommt nun erstmals der Rest Operator ins Spiel. Dieser schreibt uns sämtliche übergebene Funktionsargumente, die wir nicht bereits in benannte Variablen geschrieben haben, in eine weitere Variable mit einem beliebigen Namen:
1
const Example = (...rest) => {
2
console.log(rest);
3
};
4
Example(1, 2, 3, 4, 5);
Copied!
Ausgabe:
[1, 2, 3, 4, 5]
Dies funktioniert nicht nur als einzelnes Funktionsargument, sondern auch, wenn wir vorher bereits benannte Parameter definiert haben. Hier kümmert sich der Rest Operator dann buchstäblich um den letzten verbliebenen Rest:
1
const Example = (first, second, third, ...rest) => {
2
console.log('first:', first);
3
console.log('second:', second);
4
console.log('third:', third);
5
console.log('rest:', rest);
6
};
7
Example(1, 2, 3, 4, 5);
Copied!
Ausgabe:
first: 1 second: 2 third: 3 rest: [4, 5]
Der Rest Operator sammelt hier also die restlichen, verbliebenen Elemente aus einem Destructuring ein und speichert diese in einer Variable mit dem Namen, der hinter den drei Punkten angegeben wird. Dieser muss dabei nicht wie im obigen Beispiel rest heißen sondern kann jeden gültigen JavaScript-Variablennamen annehmen.
Das funktioniert aber nicht nur bei Funktionen sondern ebenso bei Array Destructuring:
1
const athletes = [
2
'Usain Bolt',
3
'Andre De Grasse',
4
'Christophe Lemaitre',
5
'Adam Gemili',
6
'Churandy Martina',
7
'LaShawn Merritt',
8
'Alonso Edward',
9
'Ramil Guliyev',
10
];
11
const [gold, silver, bronze, ...competitors] = athletes;
12
console.log(gold);
13
console.log(silver);
14
console.log(bronze);
15
console.log(competitors);
Copied!
Ausgabe:
1
'Usain Bolt'
2
'Andre De Grasse'
3
'Christophe Lemaitre'`
4
[
5
'Adam Gemili',
6
'Churandy Martina',
7
'LaShawn Merritt',
8
'Alonso Edward',
9
'Ramil Guliyev'
10
]
Copied!
… wie auch beim Object Destructuring:
1
const user = {
2
firstName: 'Manuel',
3
lastName: 'Bieh',
4
job: 'JavaScript Developer',
5
hair: 'Brown',
6
};
7
const { firstName, lastName, ...other } = user;
8
console.log(firstName);
9
console.log(lastName);
10
console.log(other);
Copied!
Ausgabe:
Manuel Bieh { job: 'JavaScript Developer', hair: 'Brown' }
All die Werte, die dabei nicht explizit in eine Variable geschrieben wurden während eines Destructuring Assignments, können dann in der als Rest deklarierten Variable abgerufen werden.

Template Strings

Template Strings in ES2015 sind eine „dritte Schreibweise“ für Strings in JavaScript. Bisher konnten Strings entweder in einfache Anführungszeichen ('Beispiel') oder in doppelte Anführungszeichen ("Beispiel") gesetzt werden. Nun kommt auch die Möglichkeit hinzu, diese in Backticks (`Beispiel` ) zu setzen.
Template Strings können in zwei Varianten auftreten. Als gewöhnliche Template Strings, die JavaScript Ausdrücke enthalten können, sowie in erweiterter Form als sog. Tagged Template Strings.
Tagged Template Strings sind eine deutlich mächtigere Form von Template Strings. Mit ihnen kann die Ausgabe von Template Strings mittels einer speziellen Funktion modifiziert werden. Das ist bei der gewöhnlichen Arbeit mit React erst einmal weniger wichtig.
Wollte man sie mit JavaScript-Ausdrücken oder Werten mischen, griff man in ES5 meist zu einfacher String Concatenation:
1
var age = 7;
2
var text = 'Meine Tochter ist ' + age + ' Jahre alt';
Copied!
1
var firstName = 'Manuel';
2
var lastName = 'Bieh';
3
var fullName = firstName + ' ' + lastName;
Copied!
Mit Template Strings wurde nun eine Variante von String eingeführt, die selbst wiederum JavaScript-Ausdrücke enthalten kann. Diese werden dazu innerhalb eines Template Strings in eine Zeichenkette in der Form ${ } gesetzt. Um bei den obigen Beispielen zu bleiben:
1
const age = 7;
2
const text = `Meine Tochter ist ${age} Jahre alt`;
Copied!
1
const firstName = 'Manuel';
2
const lastName = 'Bieh';
3
const fullName = `${firstName} ${lastName}`;
Copied!
Dabei können innerhalb der geschweiften Klammern sämtliche JavaScript-Ausdrücke verwendet werden. Also auch Funktionsaufrufe:
1
console.log(`Das heutige Datum ist ${new Date().toISOString()}`);
2
console.log(`${firstName.toUpperCase()} ${lastName.toUpperCase()}`);
Copied!

Promises und async/await

Promises (dt. Versprechen) sind kein grundsätzlich neues Konzept in JavaScript, in ES2015 haben sie aber erstmals Einzug in den Standard erhalten und können nativ ohne eine andere Library (z.B. q, Bluebird, rsvp.js, …) verwendet werden. Ganz grob erlauben Promises es, die asynchrone Entwicklung durch Callbacks zu linearisieren. Ein Promise bekommt eine Executor-Funktion übergeben, die ihrerseits die zwei Argumente resolve und reject übergeben bekommen, und kann einen von insgesamt drei verschiedenen Zuständen annehmen: als Initialwert ist dieser Zustand pending und je nachdem ob eine Operation erfolgreich oder fehlerhaft war, die Executor-Funktion also das erste (resolve) oder das zweite (reject) Argument ausgeführt hat, wechselt dieser Zustand zu fulfilled oder rejected. Auf die beiden Endzustände kann dann mittels der Methoden .then() und .catch() reagiert werden. Wird resolve aufgerufen, wird der then()-Teil ausgeführt; wird reject aufgerufen, werden sämtliche then()-Aufrufe übersprungen und der catch()-Teil wird stattdessen ausgeführt.
Eine Executor-Funktion muss dabei zwangsweise eine der beiden übergebenen Methoden ausführen, andernfalls bleibt das Promise dauerhaft unfulfilled, was zu fehlerhaften Verhalten und in bestimmten Fällen sogar zu Memory Leaks innerhalb einer Anwendung führen kann.
Um den Unterschied zwischen Promises und Callbacks einmal zu demonstrieren, werfen wir einen Blick auf das folgende fiktive Beispiel:
1
const errorHandler = (err) => {
2
console.error('An error occured:', err.message);
3
};
4
5
getUser(
6
id,
7
(user) => {
8
user.getFriends((friends) => {
9
friends[0].getSettings((settings) => {
10
if (settings.notifications === true) {
11
email.send(
12
'You are my first friend!',
13
(status) => {
14
if (status === 200) {
15
alert</