Dieses Kapitel beschreibt die Kernfunktionen von ES6. Diese Funktionen sind einfach zu übernehmen; die restlichen Funktionen sind hauptsächlich für Bibliotheksautoren von Interesse. Ich erkläre jede Funktion anhand des entsprechenden ES5-Codes.
var zu const/letfor zu forEach() zu for-ofarguments zu Rest-Parameternapply() zum Spread-Operator (...)Math.max()Array.prototype.push()concat() zum Spread-Operator (...)ErrorArray.prototype.indexOf zu Array.prototype.findIndexArray.prototype.slice() zu Array.from() oder dem Spread-Operatorapply() zu Array.prototype.fill()var zu const/let In ES5 deklarieren Sie Variablen mit var. Solche Variablen sind *funktionsbezogen*, ihr Gültigkeitsbereich sind die innersten umschließenden Funktionen. Das Verhalten von var ist gelegentlich verwirrend. Hier ein Beispiel
var x = 3;
function func(randomize) {
if (randomize) {
var x = Math.random(); // (A) scope: whole function
return x;
}
return x; // accesses the x from line A
}
func(false); // undefined
Dass func() undefined zurückgibt, mag überraschend sein. Sie können sehen, warum, wenn Sie den Code umschreiben, sodass er genauer widerspiegelt, was tatsächlich geschieht
var x = 3;
function func(randomize) {
var x;
if (randomize) {
x = Math.random();
return x;
}
return x;
}
func(false); // undefined
In ES6 können Sie zusätzlich Variablen mit let und const deklarieren. Solche Variablen sind *blockbezogen*, ihr Gültigkeitsbereich sind die innersten umschließenden Blöcke. let ist grob gesagt eine blockbezogene Version von var. const verhält sich wie let, erstellt aber Variablen, deren Werte nicht geändert werden können.
let und const verhalten sich strenger und werfen mehr Ausnahmen (z.B. wenn Sie auf ihre Variablen innerhalb ihres Gültigkeitsbereichs zugreifen, bevor sie deklariert wurden). Blockbezogenheit hilft dabei, die Effekte von Codefragmenten lokaler zu halten (siehe nächster Abschnitt für eine Demonstration). Und es ist Mainstream-tauglicher als Funktionsbezogenheit, was den Wechsel zwischen JavaScript und anderen Programmiersprachen erleichtert.
Wenn Sie var in der ursprünglichen Version durch let ersetzen, erhalten Sie ein anderes Verhalten
let x = 3;
function func(randomize) {
if (randomize) {
let x = Math.random();
return x;
}
return x;
}
func(false); // 3
Das bedeutet, dass Sie var in bestehendem Code nicht blind durch let oder const ersetzen können; Sie müssen beim Refactoring vorsichtig sein.
Mein Rat ist
const. Sie können es für alle Variablen verwenden, deren Werte sich nie ändern.let – für Variablen, deren Werte sich ändern.var.Weitere Informationen: Kapitel „Variablen und Gültigkeitsbereiche“.
In ES5 mussten Sie ein Muster namens IIFE (Immediately-Invoked Function Expression) verwenden, wenn Sie den Gültigkeitsbereich einer Variablen tmp auf einen Block beschränken wollten
(function () { // open IIFE
var tmp = ···;
···
}()); // close IIFE
console.log(tmp); // ReferenceError
In ECMAScript 6 können Sie einfach einen Block und eine let-Deklaration (oder eine const-Deklaration) verwenden
{ // open block
let tmp = ···;
···
} // close block
console.log(tmp); // ReferenceError
Weitere Informationen: Abschnitt „Vermeiden Sie IIFEs in ES6“.
Mit ES6 erhält JavaScript endlich Literale für String-Interpolation und mehrzeilige Strings.
In ES5 fügen Sie Werte in Strings ein, indem Sie diese Werte und String-Fragmente verketten
function printCoord(x, y) {
console.log('('+x+', '+y+')');
}
In ES6 können Sie String-Interpolation über Template-Literale verwenden
function printCoord(x, y) {
console.log(`(${x}, ${y})`);
}
Template-Literale helfen auch bei der Darstellung von mehrzeiligen Strings.
Zum Beispiel müssen Sie das tun, um einen in ES5 darzustellen
var HTML5_SKELETON =
'<!doctype html>\n' +
'<html>\n' +
'<head>\n' +
' <meta charset="UTF-8">\n' +
' <title></title>\n' +
'</head>\n' +
'<body>\n' +
'</body>\n' +
'</html>\n';
Wenn Sie die Zeilenumbrüche mit Backslashes escapen, sieht die Sache etwas besser aus (aber Sie müssen immer noch explizit Zeilenumbrüche hinzufügen)
var HTML5_SKELETON = '\
<!doctype html>\n\
<html>\n\
<head>\n\
<meta charset="UTF-8">\n\
<title></title>\n\
</head>\n\
<body>\n\
</body>\n\
</html>';
ES6-Template-Literale können sich über mehrere Zeilen erstrecken
const HTML5_SKELETON = `
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
</body>
</html>`;
(Die Beispiele unterscheiden sich in der enthaltenen Leerzeichenmenge, aber das spielt in diesem Fall keine Rolle.)
Weitere Informationen: Kapitel „Template-Literale und getaggte Templates“.
Im aktuellen ES5-Code müssen Sie bei der Verwendung von Funktionsausdrücken vorsichtig mit this sein. Im folgenden Beispiel erstelle ich die Hilfsvariable _this (Zeile A), damit this von UiComponent in Zeile B zugänglich ist.
function UiComponent() {
var _this = this; // (A)
var button = document.getElementById('myButton');
button.addEventListener('click', function () {
console.log('CLICK');
_this.handleClick(); // (B)
});
}
UiComponent.prototype.handleClick = function () {
···
};
In ES6 können Sie Pfeilfunktionen verwenden, die this nicht überschatten (Zeile A)
function UiComponent() {
var button = document.getElementById('myButton');
button.addEventListener('click', () => {
console.log('CLICK');
this.handleClick(); // (A)
});
}
(In ES6 haben Sie auch die Möglichkeit, eine Klasse anstelle einer Konstruktorfunktion zu verwenden. Dies wird später behandelt.)
Pfeilfunktionen sind besonders praktisch für kurze Callbacks, die nur Ergebnisse von Ausdrücken zurückgeben.
In ES5 sind solche Callbacks relativ umständlich
var arr = [1, 2, 3];
var squares = arr.map(function (x) { return x * x });
In ES6 sind Pfeilfunktionen viel prägnanter
const arr = [1, 2, 3];
const squares = arr.map(x => x * x);
Bei der Definition von Parametern können Sie sogar Klammern weglassen, wenn die Parameter nur ein einzelner Bezeichner sind. Somit sind (x) => x * x und x => x * x beide erlaubt.
Weitere Informationen: Kapitel „Pfeilfunktionen“.
Einige Funktionen oder Methoden geben mehrere Werte über Arrays oder Objekte zurück. In ES5 benötigen Sie immer Zwischenvariablen, wenn Sie auf diese Werte zugreifen möchten. In ES6 können Sie Zwischenvariablen durch Destrukturierung vermeiden.
exec() gibt erfasste Gruppen über ein Array-ähnliches Objekt zurück. In ES5 benötigen Sie eine Zwischenvariable (matchObj im folgenden Beispiel), auch wenn Sie nur an den Gruppen interessiert sind
var matchObj =
/^(\d\d\d\d)-(\d\d)-(\d\d)$/
.exec('2999-12-31');
var year = matchObj[1];
var month = matchObj[2];
var day = matchObj[3];
In ES6 macht die Destrukturierung diesen Code einfacher
const [, year, month, day] =
/^(\d\d\d\d)-(\d\d)-(\d\d)$/
.exec('2999-12-31');
Der leere Platz am Anfang des Array-Musters überspringt das Array-Element an Index Null.
Die Methode Object.getOwnPropertyDescriptor() gibt einen *Eigenschaftsdeskriptor* zurück, ein Objekt, das mehrere Werte in seinen Eigenschaften enthält.
In ES5 benötigen Sie selbst dann eine Zwischenvariable (propDesc im folgenden Beispiel), wenn Sie nur an den Eigenschaften eines Objekts interessiert sind
var obj = { foo: 123 };
var propDesc = Object.getOwnPropertyDescriptor(obj, 'foo');
var writable = propDesc.writable;
var configurable = propDesc.configurable;
console.log(writable, configurable); // true true
In ES6 können Sie Destrukturierung verwenden
const obj = { foo: 123 };
const {writable, configurable} =
Object.getOwnPropertyDescriptor(obj, 'foo');
console.log(writable, configurable); // true true
{writable, configurable} ist eine Abkürzung für
{ writable: writable, configurable: configurable }
Weitere Informationen: Kapitel „Destrukturierung“.
for zu forEach() zu for-of Vor ES5 haben Sie Arrays wie folgt durchlaufen
var arr = ['a', 'b', 'c'];
for (var i=0; i<arr.length; i++) {
var elem = arr[i];
console.log(elem);
}
In ES5 haben Sie die Möglichkeit, die Array-Methode forEach() zu verwenden
arr.forEach(function (elem) {
console.log(elem);
});
Eine for-Schleife hat den Vorteil, dass Sie sie abbrechen können, forEach() hat den Vorteil der Prägnanz.
In ES6 kombiniert die for-of-Schleife beide Vorteile
const arr = ['a', 'b', 'c'];
for (const elem of arr) {
console.log(elem);
}
Wenn Sie sowohl Index als auch Wert jedes Array-Elements wünschen, hat for-of dies auch über die neue Array-Methode entries() und Destrukturierung abgedeckt
for (const [index, elem] of arr.entries()) {
console.log(index+'. '+elem);
}
Weitere Informationen: Kap. „Die for-of-Schleife“.
In ES5 geben Sie Standardwerte für Parameter wie folgt an
function foo(x, y) {
x = x || 0;
y = y || 0;
···
}
ES6 hat eine schönere Syntax
function foo(x=0, y=0) {
···
}
Ein zusätzlicher Vorteil ist, dass in ES6 ein Standardwert für einen Parameter nur durch undefined ausgelöst wird, während er im vorherigen ES5-Code durch jeden falsy-Wert ausgelöst wird.
Weitere Informationen: Abschnitt „Standardwerte für Parameter“.
Eine gängige Methode, Parameter in JavaScript zu benennen, sind Objektliterale (das sogenannte *Options-Objekt-Muster*)
selectEntries({ start: 0, end: -1 });
Zwei Vorteile dieses Ansatzes sind: Der Code wird selbsterklärender und es ist einfacher, beliebige Parameter wegzulassen.
In ES5 können Sie selectEntries() wie folgt implementieren
function selectEntries(options) {
var start = options.start || 0;
var end = options.end || -1;
var step = options.step || 1;
···
}
In ES6 können Sie Destrukturierung in Parameterdefinitionen verwenden und der Code wird einfacher
function selectEntries({ start=0, end=-1, step=1 }) {
···
}
Um den Parameter options in ES5 optional zu machen, würden Sie Zeile A zum Code hinzufügen
function selectEntries(options) {
options = options || {}; // (A)
var start = options.start || 0;
var end = options.end || -1;
var step = options.step || 1;
···
}
In ES6 können Sie {} als Standardwert für einen Parameter angeben
function selectEntries({ start=0, end=-1, step=1 } = {}) {
···
}
Weitere Informationen: Abschnitt „Simulation benannter Parameter“.
arguments zu Rest-Parametern In ES5 müssen Sie die spezielle Variable arguments verwenden, wenn eine Funktion (oder Methode) eine beliebige Anzahl von Argumenten akzeptieren soll
function logAllArguments() {
for (var i=0; i < arguments.length; i++) {
console.log(arguments[i]);
}
}
In ES6 können Sie einen Rest-Parameter (args im folgenden Beispiel) über den ...-Operator deklarieren
function logAllArguments(...args) {
for (const arg of args) {
console.log(arg);
}
}
Rest-Parameter sind noch besser, wenn Sie nur an nachfolgenden Parametern interessiert sind
function format(pattern, ...args) {
···
}
Die Handhabung dieses Falls in ES5 ist umständlich
function format(pattern) {
var args = [].slice.call(arguments, 1);
···
}
Rest-Parameter machen den Code leichter lesbar: Sie können bereits an den Parameterdefinitionen erkennen, dass eine Funktion eine variable Anzahl von Parametern hat.
Weitere Informationen: Abschnitt „Rest-Parameter“.
apply() zum Spread-Operator (...) In ES5 wandeln Sie Arrays über apply() in Parameter um. ES6 hat dafür den Spread-Operator.
Math.max() Math.max() gibt den numerisch größten seiner Argumente zurück. Es funktioniert für eine beliebige Anzahl von Argumenten, aber nicht für Arrays.
ES5 – apply()
> Math.max.apply(Math, [-1, 5, 11, 3])
11
ES6 – Spread-Operator
> Math.max(...[-1, 5, 11, 3])
11
Array.prototype.push() Array.prototype.push() hängt alle seine Argumente als Elemente an seinen Empfänger an. Es gibt keine Methode, die destruktiv ein Array an ein anderes anhängt.
ES5 – apply()
var arr1 = ['a', 'b'];
var arr2 = ['c', 'd'];
arr1.push.apply(arr1, arr2);
// arr1 is now ['a', 'b', 'c', 'd']
ES6 – Spread-Operator
const arr1 = ['a', 'b'];
const arr2 = ['c', 'd'];
arr1.push(...arr2);
// arr1 is now ['a', 'b', 'c', 'd']
Weitere Informationen: Abschnitt „Der Spread-Operator (...)“.
concat() zum Spread-Operator (...) Der Spread-Operator kann auch (nicht-destruktiv) den Inhalt seines Operanden in Array-Elemente umwandeln. Das bedeutet, dass er eine Alternative zur Array-Methode concat() wird.
ES5 – concat()
var arr1 = ['a', 'b'];
var arr2 = ['c'];
var arr3 = ['d', 'e'];
console.log(arr1.concat(arr2, arr3));
// [ 'a', 'b', 'c', 'd', 'e' ]
ES6 – Spread-Operator
const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];
console.log([...arr1, ...arr2, ...arr3]);
// [ 'a', 'b', 'c', 'd', 'e' ]
Weitere Informationen: Abschnitt „Der Spread-Operator (...)“.
In JavaScript sind Methoden Eigenschaften, deren Werte Funktionen sind.
In ES5-Objektliteralen werden Methoden wie andere Eigenschaften erstellt. Die Eigenschaftswerte werden über Funktionsausdrücke bereitgestellt.
var obj = {
foo: function () {
···
},
bar: function () {
this.foo();
}, // trailing comma is legal in ES5
}
ES6 hat *Methodendefinitionen*, spezielle Syntax zum Erstellen von Methoden
const obj = {
foo() {
···
},
bar() {
this.foo();
},
}
Weitere Informationen: Abschnitt „Methodendefinitionen“.
ES6-Klassen sind größtenteils nur bequemere Syntax für Konstruktorfunktionen.
In ES5 implementieren Sie Konstruktorfunktionen direkt
function Person(name) {
this.name = name;
}
Person.prototype.describe = function () {
return 'Person called '+this.name;
};
In ES6 bieten Klassen eine etwas bequemere Syntax für Konstruktorfunktionen
class Person {
constructor(name) {
this.name = name;
}
describe() {
return 'Person called '+this.name;
}
}
Beachten Sie die kompakte Syntax für Methodendefinitionen – kein Schlüsselwort function erforderlich. Beachten Sie auch, dass es keine Kommas zwischen den Teilen einer Klasse gibt.
Subclassing ist in ES5 kompliziert, insbesondere beim Verweis auf Super-Konstruktoren und Super-Eigenschaften. Dies ist der kanonische Weg, einen Unterkonstruktor Employee von Person zu erstellen
function Employee(name, title) {
Person.call(this, name); // super(name)
this.title = title;
}
Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;
Employee.prototype.describe = function () {
return Person.prototype.describe.call(this) // super.describe()
+ ' (' + this.title + ')';
};
ES6 hat eingebaute Unterstützung für Subclassing über die extends-Klausel
class Employee extends Person {
constructor(name, title) {
super(name);
this.title = title;
}
describe() {
return super.describe() + ' (' + this.title + ')';
}
}
Weitere Informationen: Kapitel „Klassen“.
Error In ES5 ist es unmöglich, den eingebauten Konstruktor für Ausnahmen, Error, zu subclassen. Der folgende Code zeigt einen Workaround, der dem Konstruktor MyError wichtige Funktionen wie einen Stack-Trace verleiht
function MyError() {
// Use Error as a function
var superInstance = Error.apply(null, arguments);
copyOwnPropertiesFrom(this, superInstance);
}
MyError.prototype = Object.create(Error.prototype);
MyError.prototype.constructor = MyError;
function copyOwnPropertiesFrom(target, source) {
Object.getOwnPropertyNames(source)
.forEach(function(propKey) {
var desc = Object.getOwnPropertyDescriptor(source, propKey);
Object.defineProperty(target, propKey, desc);
});
return target;
};
In ES6 können alle integrierten Konstruktoren subclassed werden, weshalb der folgende Code das erreicht, was der ES5-Code nur simulieren kann
class MyError extends Error {
}
Weitere Informationen: Abschnitt „Subclassing integrierter Konstruktoren“.
Die Verwendung des Sprachkonstrukts *Objekt* als Map von Strings zu beliebigen Werten (eine Datenstruktur) war in JavaScript schon immer eine Notlösung. Der sicherste Weg, dies zu tun, besteht darin, ein Objekt zu erstellen, dessen Prototyp null ist. Dann müssen Sie immer noch sicherstellen, dass kein Schlüssel jemals der String '__proto__' ist, da dieser Eigenschaftsschlüssel in vielen JavaScript-Engines spezielle Funktionalität auslöst.
Der folgende ES5-Code enthält die Funktion countWords, die das Objekt dict als Map verwendet
var dict = Object.create(null);
function countWords(word) {
var escapedWord = escapeKey(word);
if (escapedWord in dict) {
dict[escapedWord]++;
} else {
dict[escapedWord] = 1;
}
}
function escapeKey(key) {
if (key.indexOf('__proto__') === 0) {
return key+'%';
} else {
return key;
}
}
In ES6 können Sie die integrierte Datenstruktur Map verwenden und müssen keine Schlüssel escapen. Als Nachteil ist das Inkrementieren von Werten innerhalb von Maps weniger bequem.
const map = new Map();
function countWords(word) {
const count = map.get(word) || 0;
map.set(word, count + 1);
}
Ein weiterer Vorteil von Maps ist, dass Sie beliebige Werte als Schlüssel verwenden können, nicht nur Strings.
Weitere Informationen
Die ECMAScript 6-Standardbibliothek bietet mehrere neue Methoden für Strings.
Von indexOf zu startsWith
if (str.indexOf('x') === 0) {} // ES5
if (str.startsWith('x')) {} // ES6
Von indexOf zu endsWith
function endsWith(str, suffix) { // ES5
var index = str.indexOf(suffix);
return index >= 0
&& index === str.length-suffix.length;
}
str.endsWith(suffix); // ES6
Von indexOf zu includes
if (str.indexOf('x') >= 0) {} // ES5
if (str.includes('x')) {} // ES6
Von join zu repeat (die ES5-Methode zum Wiederholen eines Strings ist eher ein Hack)
new Array(3+1).join('#') // ES5
'#'.repeat(3) // ES6
Weitere Informationen: Kapitel „Neue String-Funktionen“
Es gibt auch mehrere neue Array-Methoden in ES6.
Array.prototype.indexOf zu Array.prototype.findIndex Letzteres kann verwendet werden, um NaN zu finden, was ersteres nicht erkennen kann
const arr = ['a', NaN];
arr.indexOf(NaN); // -1
arr.findIndex(x => Number.isNaN(x)); // 1
Nebenbei bemerkt, das neue Number.isNaN() bietet eine sichere Möglichkeit, NaN zu erkennen (da es keine Nicht-Zahlen in Zahlen umwandelt)
> isNaN('abc')
true
> Number.isNaN('abc')
false
Array.prototype.slice() zu Array.from() oder dem Spread-Operator In ES5 wurde Array.prototype.slice() verwendet, um Array-ähnliche Objekte in Arrays zu konvertieren. In ES6 gibt es Array.from()
var arr1 = Array.prototype.slice.call(arguments); // ES5
const arr2 = Array.from(arguments); // ES6
Wenn ein Wert iterierbar ist (wie alle Array-ähnlichen DOM-Datenstrukturen heutzutage), können Sie auch den Spread-Operator (...) verwenden, um ihn in ein Array zu konvertieren
const arr1 = [...'abc'];
// ['a', 'b', 'c']
const arr2 = [...new Set().add('a').add('b')];
// ['a', 'b']
apply() zu Array.prototype.fill() In ES5 können Sie apply() als Hack verwenden, um ein Array beliebiger Länge zu erstellen, das mit undefined gefüllt ist
// Same as Array(undefined, undefined)
var arr1 = Array.apply(null, new Array(2));
// [undefined, undefined]
In ES6 ist fill() eine einfachere Alternative
const arr2 = new Array(2).fill(undefined);
// [undefined, undefined]
fill() ist noch bequemer, wenn Sie ein Array erstellen möchten, das mit einem beliebigen Wert gefüllt ist
// ES5
var arr3 = Array.apply(null, new Array(2))
.map(function (x) { return 'x' });
// ['x', 'x']
// ES6
const arr4 = new Array(2).fill('x');
// ['x', 'x']
fill() ersetzt alle Array-Elemente durch den angegebenen Wert. Lücken werden so behandelt, als wären sie Elemente.
Weitere Informationen: Abschn. „Arrays mit Werten füllen“
Schon in ES5 haben Modulsysteme, die auf AMD- oder CommonJS-Syntax basieren, meist handgeschriebene Lösungen wie das Revealing Module Pattern ersetzt.
ES6 hat integrierte Unterstützung für Module. Leider unterstützt noch keine JavaScript-Engine diese nativ. Aber Tools wie browserify, webpack oder jspm lassen Sie ES6-Syntax zum Erstellen von Modulen verwenden und machen den von Ihnen geschriebenen Code zukunftssicher.
In CommonJS exportieren Sie mehrere Entitäten wie folgt
//------ lib.js ------
var sqrt = Math.sqrt;
function square(x) {
return x * x;
}
function diag(x, y) {
return sqrt(square(x) + square(y));
}
module.exports = {
sqrt: sqrt,
square: square,
diag: diag,
};
//------ main1.js ------
var square = require('lib').square;
var diag = require('lib').diag;
console.log(square(11)); // 121
console.log(diag(4, 3)); // 5
Alternativ können Sie das gesamte Modul als Objekt importieren und über dieses auf square und diag zugreifen
//------ main2.js ------
var lib = require('lib');
console.log(lib.square(11)); // 121
console.log(lib.diag(4, 3)); // 5
In ES6 werden mehrere Exporte als *benannte Exporte* bezeichnet und wie folgt behandelt
//------ lib.js ------
export const sqrt = Math.sqrt;
export function square(x) {
return x * x;
}
export function diag(x, y) {
return sqrt(square(x) + square(y));
}
//------ main1.js ------
import { square, diag } from 'lib';
console.log(square(11)); // 121
console.log(diag(4, 3)); // 5
Die Syntax zum Importieren von Modulen als Objekte sieht wie folgt aus (Zeile A)
//------ main2.js ------
import * as lib from 'lib'; // (A)
console.log(lib.square(11)); // 121
console.log(lib.diag(4, 3)); // 5
Node.js erweitert CommonJS und ermöglicht den Export einzelner Werte aus Modulen über module.exports
//------ myFunc.js ------
module.exports = function () { ··· };
//------ main1.js ------
var myFunc = require('myFunc');
myFunc();
In ES6 geschieht dasselbe über einen sogenannten *Standardexport* (deklariert über export default)
//------ myFunc.js ------
export default function () { ··· } // no semicolon!
//------ main1.js ------
import myFunc from 'myFunc';
myFunc();
Weitere Informationen: Kapitel „Module“.
Nachdem Sie einen ersten Eindruck von ES6 bekommen haben, können Sie Ihre Erkundung fortsetzen, indem Sie die Kapitel durchgehen: Jedes Kapitel behandelt eine Funktion oder eine Gruppe verwandter Funktionen und beginnt mit einer Übersicht. Das letzte Kapitel sammelt all diese Übersichtsteile an einem Ort.