30. Ein Überblick über die Neuigkeiten in ES6
Inhaltsverzeichnis
Bitte unterstützen Sie dieses Buch: kaufen Sie es (PDF, EPUB, MOBI) oder spenden Sie
(Werbung, bitte nicht blockieren.)

30. Ein Überblick über die Neuigkeiten in ES6

Dieses Kapitel sammelt die Überblick-Abschnitte aller Kapitel dieses Buches.



30.1 Kategorien von ES6-Funktionen

Die Einleitung der ES6-Spezifikation listet alle neuen Funktionen auf.

Einige der [ECMAScript 6] wichtigsten Erweiterungen umfassen Module, Klassendeklarationen, lexikalische Block-Scopes, Iteratoren und Generatoren, Promises für asynchrone Programmierung, Destrukturierungsmuster und Tail Calls. Die ECMAScript-Bibliothek von integrierten Funktionen wurde erweitert, um zusätzliche Datenabstraktionen zu unterstützen, darunter Maps, Sets und Arrays mit binären numerischen Werten, sowie zusätzliche Unterstützung für Unicode-Zusatzzeichen in Strings und regulären Ausdrücken. Die integrierten Funktionen sind jetzt durch Unterklasseerstellung erweiterbar.

Es gibt drei Hauptkategorien von Funktionen.

30.2 Neue Zahlen- und Math-Funktionen

30.2.1 Neue Ganzzahl-Literale

Sie können jetzt Ganzzahlen in binärer und oktaler Notation angeben.

> 0xFF // ES5: hexadecimal
255
> 0b11 // ES6: binary
3
> 0o10 // ES6: octal
8

30.2.2 Neue Number-Eigenschaften

Das globale Objekt Number hat einige neue Eigenschaften erhalten.

30.2.3 Neue Math-Methoden

Das globale Objekt Math hat neue Methoden für numerische, trigonometrische und bitweise Operationen. Betrachten wir vier Beispiele.

Math.sign() gibt das Vorzeichen einer Zahl zurück.

> Math.sign(-8)
-1
> Math.sign(0)
0
> Math.sign(3)
1

Math.trunc() entfernt den Dezimalbruch einer Zahl.

> Math.trunc(3.1)
3
> Math.trunc(3.9)
3
> Math.trunc(-3.1)
-3
> Math.trunc(-3.9)
-3

Math.log10() berechnet den Logarithmus zur Basis 10.

> Math.log10(100)
2

Math.hypot() berechnet die Quadratwurzel der Summe der Quadrate seiner Argumente (Satz des Pythagoras).

> Math.hypot(3, 4)
5    

30.3 Neue String-Funktionen

Neue String-Methoden

> 'hello'.startsWith('hell')
true
> 'hello'.endsWith('ello')
true
> 'hello'.includes('ell')
true
> 'doo '.repeat(3)
'doo doo doo '

ES6 hat eine neue Art von String-Literalen, die *Template-Literale*.

// String interpolation via template literals (in backticks)
const first = 'Jane';
const last = 'Doe';
console.log(`Hello ${first} ${last}!`);
    // Hello Jane Doe!

// Template literals also let you create strings with multiple lines
const multiLine = `
This is
a string
with multiple
lines`;

30.4 Symbole

Symbole sind ein neuer primitiver Datentyp in ECMAScript 6. Sie werden über eine Factory-Funktion erstellt.

const mySymbol = Symbol('mySymbol');

Jedes Mal, wenn Sie die Factory-Funktion aufrufen, wird ein neues und eindeutiges Symbol erstellt. Der optionale Parameter ist ein beschreibender String, der beim Drucken des Symbols angezeigt wird (er hat keinen anderen Zweck).

> mySymbol
Symbol(mySymbol)

30.4.1 Anwendungsfall 1: eindeutige Eigenschaftsschlüssel

Symbole werden hauptsächlich als eindeutige Eigenschaftsschlüssel verwendet – ein Symbol kollidiert niemals mit einem anderen Eigenschaftsschlüssel (Symbol oder String). Sie können beispielsweise ein Objekt *iterierbar* machen (verwendbar über die for-of-Schleife und andere Sprachmechanismen), indem Sie das in Symbol.iterator gespeicherte Symbol als Schlüssel einer Methode verwenden (mehr Informationen zu Iterierbaren finden Sie im Kapitel über Iteration).

const iterableObject = {
    [Symbol.iterator]() { // (A)
        ···
    }
}
for (const x of iterableObject) {
    console.log(x);
}
// Output:
// hello
// world

In Zeile A wird ein Symbol als Schlüssel der Methode verwendet. Dieser eindeutige Marker macht das Objekt iterierbar und ermöglicht uns die Verwendung der for-of-Schleife.

30.4.2 Anwendungsfall 2: Konstanten, die Konzepte darstellen

In ECMAScript 5 haben Sie möglicherweise Strings verwendet, um Konzepte wie Farben darzustellen. In ES6 können Sie Symbole verwenden und sicher sein, dass sie immer eindeutig sind.

const COLOR_RED    = Symbol('Red');
const COLOR_ORANGE = Symbol('Orange');
const COLOR_YELLOW = Symbol('Yellow');
const COLOR_GREEN  = Symbol('Green');
const COLOR_BLUE   = Symbol('Blue');
const COLOR_VIOLET = Symbol('Violet');

function getComplement(color) {
    switch (color) {
        case COLOR_RED:
            return COLOR_GREEN;
        case COLOR_ORANGE:
            return COLOR_BLUE;
        case COLOR_YELLOW:
            return COLOR_VIOLET;
        case COLOR_GREEN:
            return COLOR_RED;
        case COLOR_BLUE:
            return COLOR_ORANGE;
        case COLOR_VIOLET:
            return COLOR_YELLOW;
        default:
            throw new Exception('Unknown color: '+color);
    }
}

Jedes Mal, wenn Sie Symbol('Red') aufrufen, wird ein neues Symbol erstellt. Daher kann COLOR_RED niemals mit einem anderen Wert verwechselt werden. Das wäre anders, wenn es der String 'Red' wäre.

30.4.3 Fallstrick: Symbole können nicht in Strings konvertiert werden

Das Konvertieren (implizites Umwandeln) von Symbolen in Strings führt zu Fehlern.

const sym = Symbol('desc');

const str1 = '' + sym; // TypeError
const str2 = `${sym}`; // TypeError

Die einzige Lösung ist die explizite Konvertierung.

const str2 = String(sym); // 'Symbol(desc)'
const str3 = sym.toString(); // 'Symbol(desc)'

Das Verhindern von Konvertierungen verhindert einige Fehler, macht aber auch die Arbeit mit Symbolen komplizierter.

Die folgenden Operationen sind sich Symbole als Eigenschaftsschlüssel bewusst.

Die folgenden Operationen ignorieren Symbole als Eigenschaftsschlüssel.

30.5 Template-Literale

ES6 hat zwei neue Arten von Literalen: *Template-Literale* und *Tagged Template Literale*. Diese beiden Literale haben ähnliche Namen und sehen ähnlich aus, sind aber sehr unterschiedlich. Daher ist es wichtig, sie zu unterscheiden.

*Template-Literale* sind String-Literale, die sich über mehrere Zeilen erstrecken und interpolierte Ausdrücke enthalten können (eingefügt über ${···}).

const firstName = 'Jane';
console.log(`Hello ${firstName}!
How are you
today?`);

// Output:
// Hello Jane!
// How are you
// today?

*Tagged Template Literale* (kurz: *Tagged Templates*) werden erstellt, indem eine Funktion vor einem Template-Literal genannt wird.

> String.raw`A \tagged\ template`
'A \\tagged\\ template'

Tagged Templates sind Funktionsaufrufe. Im vorherigen Beispiel wird die Methode String.raw aufgerufen, um das Ergebnis des Tagged Templates zu erzeugen.

30.6 Variablen und Scoping

ES6 bietet zwei neue Möglichkeiten, Variablen zu deklarieren: let und const, die die ES5-Methode zur Variablendeklaration, var, weitgehend ersetzen.

30.6.1 let

let funktioniert ähnlich wie var, aber die von ihm deklarierte Variable ist *block-scoped*, sie existiert nur innerhalb des aktuellen Blocks. var ist *function-scoped*.

Im folgenden Code können Sie sehen, dass die mit let deklarierte Variable tmp nur innerhalb des Blocks existiert, der in Zeile A beginnt.

function order(x, y) {
    if (x > y) { // (A)
        let tmp = x;
        x = y;
        y = tmp;
    }
    console.log(tmp===x); // ReferenceError: tmp is not defined
    return [x, y];
}

30.6.2 const

const funktioniert wie let, aber die von Ihnen deklarierte Variable muss sofort mit einem Wert initialisiert werden, der anschließend nicht mehr geändert werden kann.

const foo;
    // SyntaxError: missing = in const declaration

const bar = 123;
bar = 456;
    // TypeError: `bar` is read-only

Da for-of für jede Schleifeniteration ein *Binding* (Speicherplatz für eine Variable) erstellt, ist es in Ordnung, die Schleifenvariable mit const zu deklarieren.

for (const x of ['a', 'b']) {
    console.log(x);
}
// Output:
// a
// b

30.6.3 Möglichkeiten zur Deklaration von Variablen

Die folgende Tabelle gibt einen Überblick über sechs Möglichkeiten, wie Variablen in ES6 deklariert werden können (inspiriert von einer Tabelle von kangax).

  Hoisting Geltungsbereich Erzeugt globale Eigenschaften.
var Deklaration Funktion Ja
let Temporäre tote Zone Block Nein
const Temporäre tote Zone Block Nein
Funktion Vollständig Block Ja
class Nein Block Nein
import Vollständig Modul-global Nein

30.7 Destrukturierung

*Destrukturierung* ist eine bequeme Möglichkeit, mehrere Werte aus Daten zu extrahieren, die in (möglicherweise verschachtelten) Objekten und Arrays gespeichert sind. Sie kann an Stellen verwendet werden, die Daten empfangen (z. B. auf der linken Seite einer Zuweisung). Wie die Werte extrahiert werden, wird über Muster angegeben (lesen Sie weiter für Beispiele).

30.7.1 Objekt-Destrukturierung

Objekte destrukturieren.

const obj = { first: 'Jane', last: 'Doe' };
const {first: f, last: l} = obj;
    // f = 'Jane'; l = 'Doe'

// {prop} is short for {prop: prop}
const {first, last} = obj;
    // first = 'Jane'; last = 'Doe'

Destrukturierung hilft bei der Verarbeitung von Rückgabewerten.

const obj = { foo: 123 };

const {writable, configurable} =
    Object.getOwnPropertyDescriptor(obj, 'foo');

console.log(writable, configurable); // true true

30.7.2 Array-Destrukturierung

Array-Destrukturierung (funktioniert für alle iterierbaren Werte).

const iterable = ['a', 'b'];
const [x, y] = iterable;
    // x = 'a'; y = 'b'

Destrukturierung hilft bei der Verarbeitung von Rückgabewerten.

const [all, year, month, day] =
    /^(\d\d\d\d)-(\d\d)-(\d\d)$/
    .exec('2999-12-31');

30.7.3 Wo kann Destrukturierung verwendet werden?

Destrukturierung kann an den folgenden Stellen verwendet werden (ich zeige Array-Muster zur Veranschaulichung; Objekt-Muster funktionieren genauso gut).

// Variable declarations:
const [x] = ['a'];
let [x] = ['a'];
var [x] = ['a'];

// Assignments:
[x] = ['a'];

// Parameter definitions:
function f([x]) { ··· }
f(['a']);

Sie können auch in einer for-of-Schleife destrukturieren.

const arr = ['a', 'b'];
for (const [index, element] of arr.entries()) {
    console.log(index, element);
}
// Output:
// 0 a
// 1 b

30.8 Parameterbehandlung

Die Parameterbehandlung wurde in ECMAScript 6 erheblich verbessert. Sie unterstützt jetzt Standardparameterwerte, Rest-Parameter (Varargs) und Destrukturierung.

Zusätzlich hilft der Spread-Operator bei Funktions-/Methoden-/Konstruktoraufrufen und Array-Literalen.

30.8.1 Standardparameterwerte

Ein *Standardparameterwert* wird einem Parameter über ein Gleichheitszeichen (=) zugewiesen. Wenn ein Aufrufer keinen Wert für den Parameter angibt, wird der Standardwert verwendet. Im folgenden Beispiel ist der Standardparameterwert von y 0.

function func(x, y=0) {
    return [x, y];
}
func(1, 2); // [1, 2]
func(1); // [1, 0]
func(); // [undefined, 0]

30.8.2 Rest-Parameter

Wenn Sie einem Parameternamen den Rest-Operator (...) voranstellen, erhält dieser Parameter alle verbleibenden Parameter als Array.

function format(pattern, ...params) {
    return {pattern, params};
}
format(1, 2, 3);
    // { pattern: 1, params: [ 2, 3 ] }
format();
    // { pattern: undefined, params: [] }

30.8.3 Benannte Parameter durch Destrukturierung

Sie können benannte Parameter simulieren, wenn Sie mit einem Objektmuster in der Parameterliste destrukturieren.

function selectEntries({ start=0, end=-1, step=1 } = {}) { // (A)
    // The object pattern is an abbreviation of:
    // { start: start=0, end: end=-1, step: step=1 }

    // Use the variables `start`, `end` and `step` here
    ···
}

selectEntries({ start: 10, end: 30, step: 2 });
selectEntries({ step: 3 });
selectEntries({});
selectEntries();

Das = {} in Zeile A ermöglicht es Ihnen, selectEntries() ohne Parameter aufzurufen.

30.8.4 Spread-Operator (...)

Bei Funktions- und Konstruktoraufrufen wandelt der Spread-Operator iterierbare Werte in Argumente um.

> Math.max(-1, 5, 11, 3)
11
> Math.max(...[-1, 5, 11, 3])
11
> Math.max(-1, ...[-5, 11], 3)
11

In Array-Literalen wandelt der Spread-Operator iterierbare Werte in Array-Elemente um.

> [1, ...[2,3], 4]
[1, 2, 3, 4]

30.9 Aufrufbare Entitäten in ECMAScript 6

In ES5 spielte eine einzelne Konstruktion, die (traditionelle) Funktion, drei Rollen:

In ES6 gibt es mehr Spezialisierung. Die drei Aufgaben werden nun wie folgt behandelt. Bezüglich Funktionsdefinitionen und Klassendefinitionen ist eine Definition entweder eine Deklaration oder ein Ausdruck.

Besonders für Callbacks sind Pfeilfunktionen praktisch, da sie nicht den this des umgebenden Geltungsbereichs überschatten.

Für längere Callbacks und eigenständige Funktionen können traditionelle Funktionen in Ordnung sein. Einige APIs verwenden this als impliziten Parameter. In diesem Fall haben Sie keine andere Wahl, als traditionelle Funktionen zu verwenden.

Beachten Sie, dass ich unterscheide:

Auch wenn sich ihr Verhalten unterscheidet (wie später erklärt wird), sind all diese Entitäten Funktionen. Zum Beispiel:

> typeof (() => {}) // arrow function
'function'
> typeof function* () {} // generator function
'function'
> typeof class {} // class
'function'

30.10 Pfeilfunktionen

Es gibt zwei Vorteile von Pfeilfunktionen.

Erstens sind sie weniger aufwendig als traditionelle Funktionsausdrücke.

const arr = [1, 2, 3];
const squares = arr.map(x => x * x);

// Traditional function expression:
const squares = arr.map(function (x) { return x * x });

Zweitens wird ihr this aus der Umgebung übernommen (*lexikalisch*). Daher benötigen Sie bind() oder that = this nicht mehr.

function UiComponent() {
    const button = document.getElementById('myButton');
    button.addEventListener('click', () => {
        console.log('CLICK');
        this.handleClick(); // lexical `this`
    });
}

Die folgenden Variablen sind in Pfeilfunktionen alle lexikalisch.

30.11 Neue OOP-Funktionen neben Klassen

30.11.1 Neue Funktionen für Objekt-Literale

Methodendefinitionen

const obj = {
    myMethod(x, y) {
        ···
    }
};

Eigenschaftswert-Kurzschriften

const first = 'Jane';
const last = 'Doe';

const obj = { first, last };
// Same as:
const obj = { first: first, last: last };

Berechenbare Eigenschaftsschlüssel

const propKey = 'foo';
const obj = {
    [propKey]: true,
    ['b'+'ar']: 123
};

Diese neue Syntax kann auch für Methodendefinitionen verwendet werden.

const obj = {
    ['h'+'ello']() {
        return 'hi';
    }
};
console.log(obj.hello()); // hi

Der Haupteinsatzfall für berechenbare Eigenschaftsschlüssel ist die einfache Verwendung von Symbolen als Eigenschaftsschlüssel.

30.11.2 Neue Methoden in Object

Die wichtigste neue Methode von Object ist assign(). Traditionell wurde diese Funktionalität in der JavaScript-Welt extend() genannt. Im Gegensatz zur Funktionsweise dieser klassischen Operation berücksichtigt Object.assign() nur *eigene* (nicht vererbte) Eigenschaften.

const obj = { foo: 123 };
Object.assign(obj, { bar: true });
console.log(JSON.stringify(obj));
    // {"foo":123,"bar":true}

30.12 Klassen

Eine Klasse und eine Unterklasse.

class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
    toString() {
        return `(${this.x}, ${this.y})`;
    }
}

class ColorPoint extends Point {
    constructor(x, y, color) {
        super(x, y);
        this.color = color;
    }
    toString() {
        return super.toString() + ' in ' + this.color;
    }
}

Verwendung der Klassen.

> const cp = new ColorPoint(25, 8, 'green');

> cp.toString();
'(25, 8) in green'

> cp instanceof ColorPoint
true
> cp instanceof Point
true

Unter der Haube sind ES6-Klassen nichts radikal Neues: Sie bieten hauptsächlich eine bequemere Syntax zur Erstellung von Oldschool-Konstruktorfunktionen. Das sehen Sie, wenn Sie typeof verwenden.

> typeof Point
'function'

30.13 Module

JavaScript hat seit langem Module. Sie wurden jedoch über Bibliotheken implementiert und nicht in die Sprache integriert. ES6 ist das erste Mal, dass JavaScript eingebaute Module hat.

ES6-Module werden in Dateien gespeichert. Es gibt genau ein Modul pro Datei und eine Datei pro Modul. Sie haben zwei Möglichkeiten, Dinge aus einem Modul zu exportieren. Diese beiden Wege können gemischt werden, aber es ist normalerweise besser, sie getrennt zu verwenden.

30.13.1 Mehrere benannte Exporte

Es kann mehrere *benannte Exporte* geben.

//------ 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));
}

//------ main.js ------
import { square, diag } from 'lib';
console.log(square(11)); // 121
console.log(diag(4, 3)); // 5

Sie können auch das vollständige Modul importieren.

//------ main.js ------
import * as lib from 'lib';
console.log(lib.square(11)); // 121
console.log(lib.diag(4, 3)); // 5

30.13.2 Ein einzelner Standardexport

Es kann einen einzelnen *Standardexport* geben. Zum Beispiel eine Funktion.

//------ myFunc.js ------
export default function () { ··· } // no semicolon!

//------ main1.js ------
import myFunc from 'myFunc';
myFunc();

Oder eine Klasse.

//------ MyClass.js ------
export default class { ··· } // no semicolon!

//------ main2.js ------
import MyClass from 'MyClass';
const inst = new MyClass();

Beachten Sie, dass am Ende kein Semikolon steht, wenn Sie eine Funktion oder eine Klasse (die anonyme Deklarationen sind) als Standard exportieren.

30.13.3 Browser: Skripte versus Module

  Skripte Module
HTML-Element <script> <script type="module">
Standardmodus nicht-strict strict
Top-Level-Variablen sind global lokal für das Modul
Wert von this auf Top-Level Fenster undefined
Ausgeführt synchron asynchron
Deklarative Importe (import-Anweisung) nein ja
Programmatische Importe (Promise-basierte API) ja ja
Dateierweiterung .js .js

30.14 Die for-of-Schleife

for-of ist eine neue Schleife in ES6, die sowohl for-in als auch forEach() ersetzt und das neue Iterationsprotokoll unterstützt.

Verwenden Sie sie, um über *iterierbare* Objekte (Arrays, Strings, Maps, Sets usw.; siehe Kap. „Iterierbare Objekte und Iteratoren“) zu iterieren.

const iterable = ['a', 'b'];
for (const x of iterable) {
    console.log(x);
}

// Output:
// a
// b

break und continue funktionieren in for-of-Schleifen.

for (const x of ['a', '', 'b']) {
    if (x.length === 0) break;
    console.log(x);
}

// Output:
// a

Greifen Sie sowohl auf Elemente als auch auf deren Indizes zu, während Sie über ein Array iterieren (die eckigen Klammern vor of bedeuten, dass wir Destrukturierung verwenden).

const arr = ['a', 'b'];
for (const [index, element] of arr.entries()) {
    console.log(`${index}. ${element}`);
}

// Output:
// 0. a
// 1. b

Iteration über die [Schlüssel, Wert]-Einträge in einer Map (die eckigen Klammern vor of bedeuten, dass wir Destrukturierung verwenden).

const map = new Map([
    [false, 'no'],
    [true, 'yes'],
]);
for (const [key, value] of map) {
    console.log(`${key} => ${value}`);
}

// Output:
// false => no
// true => yes

30.15 Neue Array-Funktionen

Neue statische Array-Methoden.

Neue Array.prototype-Methoden.

30.16 Maps und Sets

Unter anderem sind die folgenden vier Datenstrukturen in ECMAScript 6 neu: Map, WeakMap, Set und WeakSet.

30.16.1 Maps

Die Schlüssel einer Map können beliebige Werte sein.

> const map = new Map(); // create an empty Map
> const KEY = {};

> map.set(KEY, 123);
> map.get(KEY)
123
> map.has(KEY)
true
> map.delete(KEY);
true
> map.has(KEY)
false

Sie können ein Array (oder jedes iterierbare Objekt) mit [Schlüssel, Wert]-Paaren verwenden, um die anfänglichen Daten in der Map einzurichten.

const map = new Map([
    [ 1, 'one' ],
    [ 2, 'two' ],
    [ 3, 'three' ], // trailing comma is ignored
]);

30.16.2 Sets

Ein Set ist eine Sammlung von eindeutigen Elementen.

const arr = [5, 1, 5, 7, 7, 5];
const unique = [...new Set(arr)]; // [ 5, 1, 7 ]

Wie Sie sehen können, können Sie ein Set mit Elementen initialisieren, indem Sie dem Konstruktor ein iterierbares Objekt (arr im Beispiel) mit diesen Elementen übergeben.

30.16.3 WeakMaps

Eine WeakMap ist eine Map, die ihre Schlüssel nicht vor der Garbage Collection schützt. Das bedeutet, dass Sie Objekten Daten zuordnen können, ohne sich um Speicherlecks sorgen zu müssen. Zum Beispiel:

//----- Manage listeners

const _objToListeners = new WeakMap();

function addListener(obj, listener) {
    if (! _objToListeners.has(obj)) {
        _objToListeners.set(obj, new Set());
    }
    _objToListeners.get(obj).add(listener);
}

function triggerListeners(obj) {
    const listeners = _objToListeners.get(obj);
    if (listeners) {
        for (const listener of listeners) {
            listener();
        }
    }
}

//----- Example: attach listeners to an object

const obj = {};
addListener(obj, () => console.log('hello'));
addListener(obj, () => console.log('world'));

//----- Example: trigger listeners

triggerListeners(obj);

// Output:
// hello
// world

30.17 Ganzzahlige Arrays

Ganzzahlige Arrays (Typed Arrays) sind eine ECMAScript 6 API zur Handhabung binärer Daten.

Codebeispiel.

const typedArray = new Uint8Array([0,1,2]);
console.log(typedArray.length); // 3
typedArray[0] = 5;
const normalArray = [...typedArray]; // [5,1,2]

// The elements are stored in typedArray.buffer.
// Get a different view on the same data:
const dataView = new DataView(typedArray.buffer);
console.log(dataView.getUint8(0)); // 5

Instanzen von ArrayBuffer speichern die zu verarbeitenden Binärdaten. Zwei Arten von *Views* werden verwendet, um auf die Daten zuzugreifen:

Die folgenden Browser-APIs unterstützen Ganzzahlige Arrays (Details werden in einem eigenen Abschnitt erwähnt).

30.18 Iterierbare Objekte und Iteratoren

ES6 führt einen neuen Mechanismus für die Traversierung von Daten ein: *Iteration*. Zwei Konzepte sind zentral für die Iteration:

Als Schnittstellen in TypeScript-Notation ausgedrückt, sehen diese Rollen wie folgt aus:

interface Iterable {
    [Symbol.iterator]() : Iterator;
}
interface Iterator {
    next() : IteratorResult;
}
interface IteratorResult {
    value: any;
    done: boolean;
}

30.18.1 Iterierbare Werte

Die folgenden Werte sind iterierbar:

Einfache Objekte sind nicht iterierbar (warum, wird in einem eigenen Abschnitt erklärt).

30.18.2 Konstrukte, die Iteration unterstützen

Sprachkonstrukte, die über Iteration auf Daten zugreifen:

30.19 Generatoren

30.19.1 Was sind Generatoren?

Sie können sich Generatoren als Prozesse (Code-Teile) vorstellen, die Sie anhalten und fortsetzen können.

function* genFunc() {
    // (A)
    console.log('First');
    yield;
    console.log('Second');
}

Beachten Sie die neue Syntax: function* ist ein neues „Schlüsselwort“ für *Generatorfunktionen* (es gibt auch *Generatormethoden*). yield ist ein Operator, mit dem sich ein Generator selbst anhalten kann. Darüber hinaus können Generatoren auch Eingaben empfangen und Ausgaben über yield senden.

Wenn Sie eine Generatorfunktion genFunc() aufrufen, erhalten Sie ein *Generatorobjekt* genObj, mit dem Sie den Prozess steuern können.

const genObj = genFunc();

Der Prozess wird anfangs in Zeile A angehalten. genObj.next() setzt die Ausführung fort, ein yield in genFunc() pausiert die Ausführung.

genObj.next();
// Output: First
genObj.next();
// output: Second

30.19.2 Arten von Generatoren

Es gibt vier Arten von Generatoren:

  1. Generatorfunktionsdeklarationen.
     function* genFunc() { ··· }
     const genObj = genFunc();
    
  2. Generatorfunktionsausdrücke.
     const genFunc = function* () { ··· };
     const genObj = genFunc();
    
  3. Generatormethodendefinitionen in Objektliteralen.
     const obj = {
         * generatorMethod() {
             ···
         }
     };
     const genObj = obj.generatorMethod();
    
  4. Generatormethodendefinitionen in Klassendefinitionen (Klassendeklarationen oder Klassenausdrücke).
     class MyClass {
         * generatorMethod() {
             ···
         }
     }
     const myInst = new MyClass();
     const genObj = myInst.generatorMethod();
    

30.19.3 Anwendungsfall: Implementierung von Iterierbaren

Die von Generatoren zurückgegebenen Objekte sind iterierbar; jedes yield trägt zur Sequenz der iterierten Werte bei. Daher können Sie Generatoren verwenden, um iterierbare Objekte zu implementieren, die von verschiedenen ES6-Sprachmechanismen konsumiert werden können: for-of-Schleife, Spread-Operator (...) usw.

Die folgende Funktion gibt ein iterierbares Objekt über die Eigenschaften eines Objekts zurück, ein [Schlüssel, Wert]-Paar pro Eigenschaft.

function* objectEntries(obj) {
    const propKeys = Reflect.ownKeys(obj);

    for (const propKey of propKeys) {
        // `yield` returns a value and then pauses
        // the generator. Later, execution continues
        // where it was previously paused.
        yield [propKey, obj[propKey]];
    }
}

objectEntries() wird wie folgt verwendet:

const jane = { first: 'Jane', last: 'Doe' };
for (const [key,value] of objectEntries(jane)) {
    console.log(`${key}: ${value}`);
}
// Output:
// first: Jane
// last: Doe

Wie genau objectEntries() funktioniert, wird in einem eigenen Abschnitt erklärt. Die Implementierung derselben Funktionalität ohne Generatoren ist wesentlich aufwendiger.

30.19.4 Anwendungsfall: einfacherer asynchroner Code

Sie können Generatoren verwenden, um die Arbeit mit Promises erheblich zu vereinfachen. Betrachten wir eine Promise-basierte Funktion fetchJson() und wie sie durch Generatoren verbessert werden kann.

function fetchJson(url) {
    return fetch(url)
    .then(request => request.text())
    .then(text => {
        return JSON.parse(text);
    })
    .catch(error => {
        console.log(`ERROR: ${error.stack}`);
    });
}

Mit der Bibliothek co und einem Generator sieht dieser asynchrone Code synchron aus.

const fetchJson = co.wrap(function* (url) {
    try {
        let request = yield fetch(url);
        let text = yield request.text();
        return JSON.parse(text);
    }
    catch (error) {
        console.log(`ERROR: ${error.stack}`);
    }
});

ECMAScript 2017 wird asynchrone Funktionen haben, die intern auf Generatoren basieren. Mit ihnen sieht der Code so aus:

async function fetchJson(url) {
    try {
        let request = await fetch(url);
        let text = await request.text();
        return JSON.parse(text);
    }
    catch (error) {
        console.log(`ERROR: ${error.stack}`);
    }
}

Alle Versionen können wie folgt aufgerufen werden:

fetchJson('http://example.com/some_file.json')
.then(obj => console.log(obj));

30.19.5 Anwendungsfall: Empfang von asynchronen Daten

Generatoren können über next() Eingaben über yield empfangen. Das bedeutet, dass Sie einen Generator wecken können, sobald neue Daten asynchron eintreffen, und für den Generator fühlt es sich an, als ob er die Daten synchron empfängt.

30.20 Neue Funktionen für reguläre Ausdrücke

Die folgenden Funktionen für reguläre Ausdrücke sind in ECMAScript 6 neu:

30.21 Promises für asynchrone Programmierung

Promises sind eine Alternative zu Callbacks für die Bereitstellung der Ergebnisse einer asynchronen Berechnung. Sie erfordern mehr Aufwand von den Implementierern asynchroner Funktionen, bieten aber mehrere Vorteile für die Benutzer dieser Funktionen.

Die folgende Funktion gibt ein Ergebnis asynchron über ein Promise zurück

function asyncFunc() {
    return new Promise(
        function (resolve, reject) {
            ···
            resolve(result);
            ···
            reject(error);
        });
}

Sie rufen asyncFunc() wie folgt auf

asyncFunc()
.then(result => { ··· })
.catch(error => { ··· });

30.21.1 Verketten von then()-Aufrufen

then() gibt immer ein Promise zurück, was Ihnen ermöglicht, Methodenaufrufe zu verketten

asyncFunc1()
.then(result1 => {
    // Use result1
    return asyncFunction2(); // (A)
})
.then(result2 => { // (B)
    // Use result2
})
.catch(error => {
    // Handle errors of asyncFunc1() and asyncFunc2()
});

Wie das von then() zurückgegebene Promise P erfüllt wird, hängt davon ab, was sein Callback tut

Darüber hinaus ist zu beachten, wie catch() die Fehler von zwei asynchronen Funktionsaufrufen (asyncFunction1() und asyncFunction2()) behandelt. Das heißt, unbehandelte Fehler werden weitergegeben, bis ein Fehlerhandler vorhanden ist.

30.21.2 Ausführen von asynchronen Funktionen parallel

Wenn Sie asynchrone Funktionsaufrufe über then() verketten, werden sie nacheinander ausgeführt, einer nach dem anderen

asyncFunc1()
.then(() => asyncFunc2());

Wenn Sie das nicht tun und alle sofort aufrufen, werden sie im Grunde parallel ausgeführt (ein Fork in der Unix-Prozess-Terminologie)

asyncFunc1();
asyncFunc2();

Promise.all() ermöglicht es Ihnen, benachrichtigt zu werden, sobald alle Ergebnisse vorliegen (ein Join in der Unix-Prozess-Terminologie). Seine Eingabe ist ein Array von Promises, seine Ausgabe ein einzelnes Promise, das mit einem Array der Ergebnisse erfüllt wird.

Promise.all([
    asyncFunc1(),
    asyncFunc2(),
])
.then(([result1, result2]) => {
    ···
})
.catch(err => {
    // Receives first rejection among the Promises
    ···
});

30.21.3 Glossar: Promises

Die Promise-API befasst sich mit der asynchronen Bereitstellung von Ergebnissen. Ein Promise-Objekt (kurz: Promise) ist ein Platzhalter für das Ergebnis, das über dieses Objekt geliefert wird.

Zustände

Reagieren auf Zustandsänderungen

Zustände ändern: Es gibt zwei Operationen zum Ändern des Zustands eines Promise. Nachdem Sie eine davon einmal aufgerufen haben, haben weitere Aufrufe keine Auswirkung.

30.22 Metaprogrammierung mit Proxies

Proxies ermöglichen es Ihnen, Operationen auf Objekten abzufangen und anzupassen (wie z. B. das Abrufen von Eigenschaften). Sie sind ein Metaprogrammierungs-Feature.

Im folgenden Beispiel ist proxy das Objekt, dessen Operationen wir abfangen, und handler ist das Objekt, das die Abfangvorgänge handhabt. In diesem Fall fangen wir nur eine einzige Operation ab, das get (Abrufen von Eigenschaften).

const target = {};
const handler = {
    get(target, propKey, receiver) {
        console.log('get ' + propKey);
        return 123;
    }
};
const proxy = new Proxy(target, handler);

Wenn wir die Eigenschaft proxy.foo abrufen, fängt der Handler diese Operation ab

> proxy.foo
get foo
123

Konsultieren Sie die Referenz für die vollständige API für eine Liste der abfangbaren Operationen.

Weiter: Anmerkungen