W JavaScript ES6 jaka jest różnica między iteratorem a iteratorem?

14

Czy iterowalność jest taka sama jak iterator, czy też są różne?

Wydaje się, że ze specyfikacji iterowalny jest obiekt, powiedzmy, objtaki, który obj[Symbol.iterator]odnosi się do funkcji, więc po wywołaniu zwraca obiekt, który ma nextmetodę, która może zwrócić {value: ___, done: ___}obiekt:

function foo() {
    let i = 0;
    const wah = {
        next: function() {
            if (i <= 2) return { value: (1 + 2 * i++), done: false }
            else return { value: undefined, done: true }
        }
    };
    return wah;     // wah is iterator
}

let bar = {}        // bar is iterable

bar[Symbol.iterator] = foo;

console.log([...bar]);             // [1, 3, 5]   
for (a of bar) console.log(a);     // 1 3 5 (in three lines)

Tak więc w powyższym kodzie barjest iterowalny i wahjest iteratorem, a także next()interfejsem iteratora.

Tak więc iterowalność i iterator to różne rzeczy.

Teraz jednak we wspólnym przykładzie generatora i iteratora:

function* gen1() {
    yield 1;
    yield 3;
    yield 5;
}

const iter1 = gen1();

console.log([...iter1]);                           // [1, 3, 5]
for (a of iter1) console.log(a);                   // nothing

const iter2 = gen1();
for (a of iter2) console.log(a);                   // 1 3 5 (in three lines)

console.log(iter1[Symbol.iterator]() === iter1);   // true

W powyższym przypadku gen1jest to generator i iter1iterator, iter1.next()który wykona właściwą pracę. Ale iter1[Symbol.iterator]daje funkcję, która po wywołaniu zwraca iter1, która jest iteratorem. Czy iter1w tym przypadku jest zarówno iterowalny, jak i iterator?

Poza tym iter1różni się od powyższego przykładu 1, ponieważ iterowalność w przykładzie 1 może dawać [1, 3, 5]tyle razy [...bar], ile iter1potrzeba , podczas gdy jest iterowalna, ale ponieważ zwraca się, która za każdym razem jest tym samym iteratorem, da tylko [1, 3, 5]raz.

Możemy więc powiedzieć, w przypadku iteracji bar, ile razy można [...bar]dać wynik [1, 3, 5]- i odpowiedź brzmi: to zależy. Czy iterowalność jest taka sama jak iterator? Odpowiedź brzmi: są to różne rzeczy, ale mogą być takie same, gdy iterowalny używa siebie jako iteratora. Czy to jest poprawne?

niepolarność
źródło
Czy iter1w tym przypadku jest zarówno iterowalny, jak i iterator? ” - tak. Wszystkie natywne iteratory są również iterowalne, zwracając się, dzięki czemu można łatwo przekazać je do konstrukcji oczekujących iteracji.
Bergi
Możliwy duplikat różnicy między Iteratorem a Iterable
Felix Kling

Odpowiedzi:

10

Tak, iterables i iteratory są różne rzeczy, ale większość iteratory (w tym wszystkie te, które można uzyskać z samego JavaScript, taki jak z keyslub valuesmetod na Array.prototypelub generatorów z funkcji generatora) dziedziczą z % IteratorPrototype% obiektu , który ma Symbol.iteratormetody takie jak to:

[Symbol.iterator]() {
    return this;
}

W rezultacie wszystkie standardowe iteratory są iterowalne. Dzięki temu możesz używać ich bezpośrednio lub używać ich w for-ofpętlach i tym podobnych (które oczekują iteracji, a nie iteratorów).

Rozważ keysmetodę tablic: zwraca iterator tablicy, który odwiedza klucze tablicy (jej indeksy, jako liczby). Zauważ, że zwraca iterator . Ale powszechnym zastosowaniem jest:

for (const index of someArray.keys()) {
    // ...
}

for-ofbierze iterable , a nie iterator , więc dlaczego to działa?

Działa, ponieważ iterator jest również iterowalny; Symbol.iteratorwłaśnie wraca this.

Oto przykład, którego używam w rozdziale 6 mojej książki: Jeśli chcesz zapętlić wszystkie wpisy, ale pomiń pierwszy, a nie chcesz slicewycinać podzbioru, możesz pobrać iterator, odczytać pierwszą wartość, następnie przekaż do for-ofpętli:

const a = ["one", "two", "three", "four"];
const it = a[Symbol.iterator]();
// Skip the first one
it.next();
// Loop through the rest
for (const value of it) {
    console.log(value);
}

Zauważ, że są to wszystkie standardowe iteratory. Czasami ludzie pokazują przykłady ręcznie kodowanych iteratorów, takich jak to:

Iterator zwrócony przez rangenie nie iterable, więc to się nie powiedzie, gdy staramy się wykorzystywać go for-of.

Aby umożliwić iterację, musimy:

  1. Dodaj Symbol.iteratormetodę na początku powyższej odpowiedzi lub
  2. Spraw, aby odziedziczył po% IteratorPrototype%, który ma już tę metodę

Niestety TC39 postanowił nie zapewniać bezpośredniego sposobu uzyskania obiektu% IteratorPrototype%. Istnieje sposób pośredni (pobranie iteratora z tablicy, a następnie pobranie jego prototypu, który jest zdefiniowany jako% IteratorPrototype%), ale jest to ból.

Ale i tak nie ma potrzeby ręcznego pisania iteratorów; wystarczy użyć funkcji generatora, ponieważ generator, który zwraca, jest iterowalny:


W przeciwieństwie do tego, nie wszystkie iteratory są iteratorami. Tablice są iterowalne, ale nie iteratory. Podobnie jak łańcuchy, mapy i zestawy.

TJ Crowder
źródło
0

Odkryłem, że istnieją bardziej precyzyjne definicje terminów i są to bardziej ostateczne odpowiedzi:

Zgodnie ze specyfikacją ES6 i MDN :

Kiedy mamy

function* foo() {   // note the "*"
    yield 1;
    yield 3;
    yield 5;
}

foonazywa się funkcją generatora . A potem, kiedy mamy

let bar = foo();

barjest generatorem obiektu . A obiekt generatora jest zgodny zarówno z protokołem iteracyjnym, jak i protokołem iteratora .

Prostszą wersją jest interfejs iteratora, który jest tylko .next()metodą.

Iterable protokół jest: dla obiektu obj, obj[Symbol.iterator]daje „zero argumentów funkcja, która zwraca obiekt, zgodny z protokołem iteracyjnej”.

Według tytułu łącza MDN wydaje się, że możemy również nazwać obiekt generatora „generatorem”.

Zauważ, że w książce Nicolasa Zakasa Zrozumienie ECMAScript 6 prawdopodobnie luźno nazwał „funkcję generatora” jako „generator”, a „obiekt generatora” jako „iterator”. Punktem wyjścia jest to, że tak naprawdę oba są związane z „generatorem” - jeden jest funkcją generatora, a drugi jest obiektem generatora lub generatorem. Obiekt generatora jest zgodny zarówno z protokołem iterowalnym, jak i protokołem iteratora.

Jeśli jest to tylko obiekt zgodny z protokołem iteratora , nie można użyć [...iter]lub for (a of iter). Musi to być obiekt zgodny z iterowalnym protokołem.

I jest też nowa klasa Iterator, w przyszłych specyfikacjach JavaScript, która wciąż jest w wersji roboczej . Ma większy interfejs, w tym metod, takich jak forEach, map, reduceaktualny interfejs tablicy i nowych, jak i take, i drop. Bieżący iterator odnosi się do obiektu tylko z nextinterfejsem.

Aby odpowiedzieć na pierwotne pytanie: jaka jest różnica między iteratorem a iterowalnym, odpowiedź brzmi: iterator to obiekt z interfejsem .next(), a iterowalny to obiekt objtaki, który obj[Symbol.iterator]może dać funkcję zerowego argumentu, która po wywołaniu zwraca iterator.

Generator jest zarówno iterowalny, jak i iteracyjny, aby do tego dodać.

niepolarność
źródło