Einmal 42 und zurück > ES6/ES7/ES8 Features
ES6/ES7/ES8 Features
ES6(ECMAScript 2015), ES7(ECMAScript 2016) und ES8(ECMAScript 2017) bringen einen Haufen Vorteile gegenüber normalem Javascript. In diesem Artikel möchte ich die praktischsten vorstellen.
ES6
ES6 wurde im Jahre 2015 finalisiert und war das bedeutendste Upgrade der JavaScript Programmiersprache. Dabei wurden viele Konzepte aus anderen Programmiersprachen übernommen und Funktionen aus externen Bibliotheken in den Sprachkern integriert. Während alte Applikationen und Skript auch unter ES6 weiter funktionierten, ergaben sich viele neue Möglichkeiten, mit weniger Aufwand schnelleren und übersichtlicheren Code zu generieren.
Neue Methoden
Diversen Standardobjekten wurden neue Methoden verpasst. Speziell beim String-Datentyp kann man sich nun in vielen Stellen das Gehempel mit regulären Ausdrücken für Standardabfragen sparen. Die Beispiele sollten selbsterklärend sein. Zu den SafeIntegers empfehle ich den Artikel in der Mozilla MDN.
// String "Hallo".startsWith("He"); "Features".endsWith("es"); "ha".repeat(2); // ==> "haha" "abcdef".includes("bcd"); // Number Number.EPSILON; Number.MAX_SAFE_INTEGER; // INT_MAX bei c Number.MIN_SAFE_INTEGER; // INT_MIN bei c Number.isNan(x); Number.isFinite(x); Number.isInteger(x); Number.isSafeInteger(x); Number.parseFloat(x); Number.parseInt(x); let a = { b: 2}; Object.extend(a, {c: 5, d:1}, {c: 42}); // äquivalent $.extend() // ==> a = { b: 2, c: 42, d: 1}
let
Bei ES5 waren nur Variablendeklarationen mit dem var-Statement möglich und waren in der gesamten aktuellen Funktion sichtbar (function scope). Speziell bei for-Schleifen oder innerhalb von Blöcken konnte dies zu Problemen führen und machte die Programmierung unübersichtlich. Ab ES6 sind mit dem let-Schlüsselwort ganz normale Blockvariablen (beispielsweise wie in C) möglich.
// Klassisch function abc() { var b = 25; // Variable a gibt es auch nach der For-Schleife noch! for(var a=0; a<10; a++) { // Temporäre Variable anlegen. // Überschreibt leider den Wert von Variable b von oben. var b = a*a; console.log(b); } console.log(b); // Hier wird 81 ausgegeben. }
// ES6 function abc() { var b = 25; // Variable a gibt nur innerhalb der For-Schleife. for(let a=0; a<10; a++) { // Temporäre Variable anlegen. // Diese existiert nur in diesem Block und hat // nichts mit b von oben zu tun. let b = a*a; console.log(b); } console.log(b); // Hier wird 25 ausgegeben. }
const / freeze / seal
Bis dato fehlte ein const-Ausdruck für Variablen in JavaScript. Dies wurde nun zusammen mit freeze und seal ergänzt.
// ES6 // Konstantes Objekt erzeugen. const a = { b: 25 }; a = 42; // Erzeugt einen TypeError // Die Werte eines konstanten Objekts können sich // jedoch noch ändern. a.b = 42; // funktioniert // Gegen weitere Änderung kann man das Objekt einfrieren Object.freeze(a); // Nun sind weder Änderungen an Struktur noch an den // Werten möglich a.b = 43; // TypeError a.newParam = 10; // TypeError // Möchte man nur die Struktur verriegeln, Werteänderungen // aber weiter zulassen, so gibt es die seal-Methode. var b = {x: 5}; Object.seal(b);
Array-Funktionen
Die Methode Array.from(x) kann aus so ziemlich allem, was indizes und eine Längenangabe hat, ein Array erzeugen.
Array.from("abc"); // ==> Array["a","b","c"] let arrayObject = { 0: 'null', 1: 'eins', 2: 'zwei', 'length': 3 }; Array.from(arrayObject); // Array ["null", "eins", "zwei"]
Zudem hat das Array-Objekt ein paar neue Methoden bekommen.
['k','l','m'].keys() // ==> Array Iterator {} [...['k','l','m'].keys()] // ==> Array [0, 1, 2] Array.from(['k','l','m'].entries()) // ==> [[0, "k"], [1, "l"], [2, "m"]] Array.from("abc"); // ==> Array["a","b","c"] let arrayObject = { 0: 'null', 1: 'eins', 2: 'zwei', 'length': 3 }; Array.from(arrayObject); // Array ["null", "eins", "zwei"] [1, 5, 50 ,15].find(x => x>25); // 50 [1, 5, 50 ,15].findIndex(x => x>25); // 2
Arrow-Funktionen
Man kann sich bei Funktionen einen haufen Schreibarbeit sparen, speziell bei Callbacks.
// ES5 var f = function(x){ return x+1; } // ES6 let f = x => x + 1; // Mit 2 Parametern: let g = (x, y) => x + y; // Ohne Parameter let g = () => 42;
Arrow-Funktionen haben einen weiteren Vorteil. Sie verwenden den this-Kontext von der übergeordneten Funktion. Konstrukte wie .bind(this), that oder self entfallen damit!
Methoden in Objekten
Bei Methoden innerhalb von Objekten kann nun das Schlüsselwort function weggelassen werden.
// Klassisch var object1 = { a: function(x){ return x+25; } }; // ES6 let object1 = { a(x) { return x+25; } };
super
Von abgeleiteten Klassen (entweder TypeScript oder per abc.prototype) kann nun die übergeordnete Methode aufgerufen werden.
// ES6 let object3 = { toString() { return "Ich bin ein " + super.toString(); } }; console.log(object3.toString()); // ==> Ich bin ein [object Object]
Berechnete properties
ES6 unterstützt zum ersten Mal computed properties. Damit lassen sich ähnliche Felder oder Methoden definieren.
// ES6 let object4 = { ['field' + ind]: 'this is field'+ind; // Logarithmus von 'value' zur Basis 'base' ['log' + base](value) { return Math.log(value) / Math.log(base); } }; console.log(object4.field100); // ==> this is field 100 console.log(object4.log2(8)); // ==> 3 (Logarithmus zur Basis 2 von 8)
Klassen und Vererbung
ES6 unterstützt Klassen und deren Vererbung. Das ganze sieht ziemlich ähnlich zu PHP aus.
// ES6 class Jedi{ constructor()= { this.forceIsDark = false; } toString() { return (this.forceIsDark ? 'Join' : 'Fear is the path to')+ ' the dark side'; } }; class Sith extends Jedi { constructor() { super(); this.forceIsDark = true; } } let yoda = new Jedi(); let maul = new Sith(); console.log(maul instanceof Sith); // ==> true console.log(maul instanceof Jedi); // ==> true console.log(yoda.toString()); // ==> Fear is the path to the dark side console.log(maul.toString()); // ==> Join the dark side
Außerdem unterstützt ES6 statische Felder in Klassen.
// ES6 class Foo{ static f(x){ return x+42; } } console.log(Foo.f());
Eine Sache gibt es bei Klassen jedoch zu beachten: Im Gegensatz zu Funktionen sind Klassen erst ab der Zeile verfügbar, wo sie deklariert wurden. Sie werden also nicht wie Funktionen nach oben gezogen (hoisting).
Getter/Setter in Klassen
Man kennts schon von c#, nun geht das ganze auch in ES6.
// ES6 class Foo{ constructor(){ this.r = 40 } get radius() { return this.r; } get durchmesser() { return this.r*2; } set radius(val){ this.r = val; } } console.log(Foo.f());
Iteratoren
Über ein Array oder Objekt zu iterieren war bei ES5 ein ziemlicher Krampf. ES6 bietet nun endlich entsprechende Mechaniken.
// ES5 var arr = ['1','2','3']; for(var i in arr) if(arr.hasOwnProperty(i)) console.log(i); // ES6 for(let i of ['1','2','3']) console.log(i);
In diesem Zusammenhang gibt es noch ein paar weitere merkwürdige Iteratoren.
// Spread operator [..."abcd"]; // ==> Array["a","b","c","d"] // Destructure assignment [a,b] = "xy"; // ==> a="x" , b="y"
Maps
Dies ist das Äquivalent zu HashMap Strukturen in C#. Während ES5 das ganze mit Objekten emuliert hat, geht es bei ES6 nun nativ.
var m = new Map([ [1, 'erster'], [{}, 'zweiter'] ]); m.set("AA", "dritter"); m.delete(1); console.log(m.size); // ==> 2 m.forEach((val, key) ==> console.log(val)); m.clear();
Gleicheit bei den Schlüsseln (die übrigens sogar Funktionen sein können) wird mit dem === Operator geprüft. Daher ist {} !== {} !!!!
Sets
Im Prinzip wie Maps, jedoch nur mit value und ohne Key.
var s = new Set(["blau", "gelb"]); s.add("lila"); s.has("blau"); // ==> true [...s]; // ==> Array ["blau", "gelb", "lila" ]
Generatoren
Man kann nun Generatorfunktionen erzeugen. Mittels yield kehrt man zurück zum Aufrufenden, der interne Zustand wird aber beibehalten und beim nächsten Aufruf macht man nach dem yield weiter.
function *genFour() { yield 1; yield 2; yield 3; return 4; } // Verwendung als Iterator let four = genFour(); four.next(); // ==> { value: 1, done: false} four.next(); // ==> { value: 2, done: false} four.next(); // ==> { value: 3, done: false} four.next(); // ==> { value: 4, done: true} four.next(); // ==> { value: undefined , done: true} // Verwendung als Iterable [...genFour()]; // ==> Array [1, 2, 3, 4]
Promises
Um dem Callback-Hell zu entgehen, haben jQuery und andere Bibliotheken vor einigen Jahren Promises eingeführt. Diese sind jetzt auch nativ in der Sprache verfügbar.
requestFile() .then(function(result){ return readFile(result); } .then(function(result2){ .... return anotherresult; } .catch(function(error){ ...do something....; }
Sofern eine Funktion ein thenable-Object zurückliefert, wird der nachfolgende then-Block erst aufgerufen, wenn ein Ergebnis aus dem thenable-Object vorliegt. Oben im Beispiel sieht man das an dem readFile-Aufruf. Ein Promise rezeugt man relativv einfach:
new Promise(function(resolve, reject) { if(checkSomething()){ resolve(cargoValue); } else { reject(cargoValue); } });
Dabei kann ein Promise 3 Zustände annehmen: Pending, Resolved(fulfilled) oder Rejected.
BTW: Wenn man einen ganzen Haufen von Promises hat, die erfüllt werden müssen, bevor es weitergehen kann, sollte man die Methode Promise.all([]) verwenden, die ein Array von Promises annimmt und erst den then-Teil ausführt, wenn alle Promises den Status Pending verlassen haben.
Destructuring
…oder wie nehme ich Strukturen und Objekte wieder auseinander…?
Man stelle sich vor, man hat eine Struktur (Objekt) und möchte daraus ein paar Werte extrahieren.
var a = {x:1, y:2, z:3}; // ES5 var x = a.x; // Gleicher Name var n = a.z; // Unterschiedlicher Name console.log(x); // ==> 1 console.log(n); // ==> 3 // ES6 let {x:x, z:n} = a; // x -> x, z -> n console.log(x); // ==> 1 console.log(n); // ==> 3
Bisher recht unspektakulär. Interessant wird das ganze aber, wenn einzelne properties nicht im Objekt vorkommen. Dann kann man nämlich Default-Werte definieren.
// ES6 let a = {x:1, y:2}; let {x:x = 40, z:n = 42 } = a; // x -> x, z -> n console.log(x); // ==> 1 console.log(n); // ==> 42
Die Default-Werte werden lazy ausgewertet, das heißt, ihr Wert wird erst bestimmt, wenn er wirklich benötigt wird. Da man bei den Default-Werten auch Funktionen angeben kann, wird die Funktion erst aufgerufen, wenn der Default-Wert benötigt wird.
Es gibt beim Destructuring noch einen Haufen weitere Möglichkeiten, beispielsweise mit Arrays oder der Extraktion verbleibender Werte, das würde aber an dieser Stelle den Rahmen sprengen. Außerdem hat https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Operators/Destrukturierende_Zuweisung eine sehr gute Übersicht über die Möglichkeiten.
ES7
Neue Methoden
Diversen Standardobjekten wurden neue Methoden verpasst.
// String "5".padLeft(4); // ==> " 5" "1".padRight(2); // ==> "1 " "3".padLeft(2,"0"); // ==> "03"
let numbers = [2, 3, 5, 9, 10]; // ES6 if(numbers.indexOf(5) !== -1) console.log("5 ist drin"); // ES7 if(numbers.includes(5)) console.log("5 ist drin");
In einer früheren Version dieser Seite stand oben numbers.contain(5), was natürlich falsch ist. Vielen Dank an L3P3 für die Fehlermeldung!
Exponent-Operator
Seit ES7 gibt es einen Exponential-Operator.
// ES7 let n = 2 ** 10; // ==> 1024
ES8
async und await
Nachdem C# diese beiden Schlüsselwörter eingeführt hat, war es nur eine Frage der Zeit, wann das ganze in andere Programmiersprachen rüberschwappt. Man kann nun Methoden oder Funktionen als async deklarieren. Diese Funktionen müssen dann ein Promise zurückgeben.
Mit dem await-Schlüsselwort kann man nun darauf warten, dass das Promise fertig wird.
// ES7 async function getData() { ... } (async function() { await loadData(); console.log("Data successfully loaded!"); }());
Sonstiges
ES8 hat auch noch ein Event-Modell mit Observern und Subscribern, die Infos darüber (und über sinnvolle Anwendungsfälle) halten sich jedoch derzeit noch in Grenzen.
Zudem wurden neue Primitiven eingeführt und die Möglichkeit, eigene Einheiten (z.B. Längeneinheiten) zu definieren.
Eine weitere Möglichkeit besteht in der Operatoren-Überladung. Speziell, wenn man mit Vektoren und Matritzen arbeitet, könnte man damit vermutlich ganz nette Dinge bauen.
Bei ES8 muss man sich jedoch im Klaren darüber sein, dass es sich um bisher experimentelle Features handelt. Eine abschließende Übersicht bietet https://developer.mozilla.org/en-US/docs/Web/JavaScript/New_in_JavaScript/ECMAScript_Next_support_in_Mozilla.