Iterations Protokolle
Einige Änderungen in ECMAScript 2015 sind keine neuen Objekte oder Syntax, sondern Protokolle. Diese Protokolle können von jedem Objekt implementiert werden, wenn einige Konventionen eingehalten werden.
Es gibt zwei Protokolle: Das Iterierbare (iterable) Protokoll und das Iterator Protokoll
Das Iterierbare (iterable) Protokoll
Das Iterierbare (iterable) Protokoll erlaubt es bei JavaScript-Objekten das Iterierverhalten zu definieren oder anzupassen, wie z. B. wie Werte in einem for..of
Konstrukt durchlaufen werden. Einige Standardtypen sind von sich aus schon Iterierbar mit einem Standarditerationsverhalten, so wie Array
oder Map
, wohingegen andere Typen (so wie Object
) nicht Iterierbar sind.
Um Iterierbar zu sein, muss ein Objekt die @@iterator Methode implementieren, was bedeutet, dass das Objekte (oder ein Objekt weiter oben in der Prototypenkette (en-US)) eine Eigenschaft mit dem @@iterator Schlüssel haben muss, welcher über die Konstante
erreichbar ist:Symbol.iterator
(en-US)
Eigenschaft | Wert |
---|---|
[Symbol.iterator] |
Eine Funktion ohne Parameter, welche ein Objekt zurückgibt, welches dem Iterator Protokoll entspricht. |
Immer wenn ein Objekt iteriert werden soll (z. B. am Anfang einer for..of
Schleife), wird die @@iterator
Methode mit keinem Argument aufgerufen und der zurückgegebene Iterator wird benutzt, um die zu iterierenden Werte zu bekommen.
Das Iterator Protokoll
Das Iterator Protokoll definiert einen Standardweg, um eine Sequenz von Werte (endlich oder unendlich lang) zu produzieren.
Ein Objekt ist ein Iterator, wenn es die Methode next()
mit folgender Semantik implementiert:
Eigenschaft | Wert |
---|---|
next |
Eine Funktion ohne Parameter, welche ein Objekt mit zwei Eigenschaften zurückgibt:
Die |
Einige Iteratoren sind wiederum iterierbar:
var someArray = [1, 5, 7];
var someArrayEntries = someArray.entries();
someArrayEntries.toString(); // "[object Array Iterator]"
someArrayEntries === someArrayEntries[Symbol.iterator](); // true
Beispiele für den Einsatz de Iterations Protokolle
Ein String
ist ein Beispiel für ein standard iterierbares Objekt:
var someString = 'hi';
typeof someString[Symbol.iterator]; // "function"
Der Standard Iteratior (en-US) von String
s gibt die Codepoints einen nach dem anderen zurück:
var iterator = someString[Symbol.iterator]();
iterator + ''; // "[object String Iterator]"
iterator.next(); // { value: "h", done: false }
iterator.next(); // { value: "i", done: false }
iterator.next(); // { value: undefined, done: true }
Einige Standardkonstrukte, wie z. B. die Spread Syntax (en-US), benutzen unter der Decke das selbe Iterierbare Protokoll
[...someString] // ["h", "i"]
Man kann das Iterierverhalben neu definieren indem eine eigene @@iterator
Eigenschaft definiert wird:
var someString = new String('hi'); // need to construct a String object explicitly to avoid auto-boxing
someString[Symbol.iterator] = function() {
return { // this is the iterator object, returning a single element, the string "bye"
next: function() {
if (this._first) {
this._first = false;
return { value: 'bye', done: false };
} else {
return { done: true };
}
},
_first: true
};
};
Zu bemerken ist, dass die Neudefinition von @@iterator
hat Effekte auf Standardkonstrukte, die das Iterierbare Protokoll benutzen;
[...someString]; // ["bye"]
someString + ''; // "hi"
Iterierbare Beispiele
Standard iterierbare Objekte
String
, Array
, TypedArray
, Map
und Set
sind im Standard iterierbar, weil jeder Prototyp der Objekte eine @@iterator
Methode definiert.
Benutzerdefiniertes Iterierbares Objekt
Man kann eigene Iterierbare Objekte wie folgt erstellen:
var myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...myIterable]; // [1, 2, 3]
Standard APIs akzeptieren Iterierbare Objekte
Es gibt viele APIs, die iterierbare Objekte akzeptieren, zum Beispiel: Map([iterable])
, WeakMap([iterable])
, Set([iterable])
and WeakSet([iterable])
:
var myObj = {};
new Map([[1, 'a'], [2, 'b'], [3, 'c']]).get(2); // "b"
new WeakMap([[{}, 'a'], [myObj, 'b'], [{}, 'c']]).get(myObj); // "b"
new Set([1, 2, 3]).has(3); // true
new Set('123').has('2'); // true
new WeakSet(function* () {
yield {};
yield myObj;
yield {};
}()).has(myObj); // true
Zudem sollten Promise.all(iterable)
, Promise.race(iterable)
, and Array.from()
angeschaut werden.
Syntaxen die iterierbare Objekte erwarten
Einige Statements und Ausdrücke erwarten iterierbare Objekt, zum Beispiel die for-of
Schleife, Spread Syntax (en-US), yield*
und destrukturierende Zuweisungen:
for(let value of ['a', 'b', 'c']){
console.log(value);
}
// "a"
// "b"
// "c"
[...'abc']; // ["a", "b", "c"]
function* gen() {
yield* ['a', 'b', 'c'];
}
gen().next(); // { value:"a", done:false }
[a, b, c] = new Set(['a', 'b', 'c']);
a // "a"
Nicht richtig definierte iterierbare Objekte
Wenn eine iterierbare @@iterator
Methode keinen Iterator Objekt zurück gibt, ist es nicht richtig definiert. Wenn es so benutzt wird, führt das zu Laufzeitfehlern oder unerwartetem Verhalten:
var nonWellFormedIterable = {}
nonWellFormedIterable[Symbol.iterator] = () => 1
[...nonWellFormedIterable] // TypeError: [] is not a function
Iterator Beispiele
Einfacher Iterator
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{done: true};
}
};
}
var it = makeIterator(['yo', 'ya']);
console.log(it.next().value); // 'yo'
console.log(it.next().value); // 'ya'
console.log(it.next().done); // true
Unendlicher Iterator
function idMaker() {
var index = 0;
return {
next: function(){
return {value: index++, done: false};
}
};
}
var it = idMaker();
console.log(it.next().value); // '0'
console.log(it.next().value); // '1'
console.log(it.next().value); // '2'
// ...
Mit einem Generator
function* makeSimpleGenerator(array) {
var nextIndex = 0;
while (nextIndex < array.length) {
yield array[nextIndex++];
}
}
var gen = makeSimpleGenerator(['yo', 'ya']);
console.log(gen.next().value); // 'yo'
console.log(gen.next().value); // 'ya'
console.log(gen.next().done); // true
function* idMaker() {
var index = 0;
while (true)
yield index++;
}
var gen = idMaker();
console.log(gen.next().value); // '0'
console.log(gen.next().value); // '1'
console.log(gen.next().value); // '2'
// ...
Mit ES2015 Klassen (class)
class SimpleClass {
constructor(data) {
this.index = 0;
this.data = data;
}
[Symbol.iterator]() {
return {
next: () => {
if (this.index < this.data.length) {
return {value: this.data[this.index++], done: false};
} else {
this.index = 0; //If we would like to iterate over this again without forcing manual update of the index
return {done: true};
}
}
}
};
}
const simple = new SimpleClass([1,2,3,4,5]);
for (const val of simple) {
console.log(val); //'0' '1' '2' '3' '4' '5'
}
Ist ein Generator Objekt ein Iterator oder ein iterierbares Objekt?
Ein Generatorobjekt ist beides, Iterator und Iterierbar:
var aGeneratorObject = function* () {
yield 1;
yield 2;
yield 3;
}();
typeof aGeneratorObject.next;
// "function", because it has a next method, so it's an iterator
typeof aGeneratorObject[Symbol.iterator];
// "function", because it has an @@iterator method, so it's an iterable
aGeneratorObject[Symbol.iterator]() === aGeneratorObject;
// true, because its @@iterator method returns itself (an iterator), so it's an well-formed iterable
[...aGeneratorObject];
// [1, 2, 3]
Spezifikationen
Spezifikation | Status | Kommentar |
---|---|---|
ECMAScript 2015 (6th Edition, ECMA-262) Die Definition von 'Iteration' in dieser Spezifikation. |
Standard | Initiale Definition. |
ECMAScript (ECMA-262) Die Definition von 'Iteration' in dieser Spezifikation. |
Lebender Standard |
Siehe auch
- Für mehr Informationen über ES2015 Generatoren, siehe die function* Documentation.