No JavaScript ES6, qual é a diferença entre um iterável e um iterador?


14

Um iterável é o mesmo que um iterador ou eles são diferentes?

Parece que, pelas especificações , um iterável é um objeto, por exemplo, objque obj[Symbol.iterator]se refere a uma função, de modo que, quando invocado, retorna um objeto que possui um nextmétodo que pode retornar um {value: ___, done: ___}objeto:

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)

Portanto, no código acima, baré o iterável, waho iterador e a next()interface do iterador.

Então, iterável e iterador são coisas diferentes.

Agora, no entanto, em um exemplo comum de gerador e iterador:

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

No caso acima, gen1é o gerador e iter1o iterador, e iter1.next()fará o trabalho adequado. Mas iter1[Symbol.iterator]fornece uma função que, quando invocada, devolve iter1, que é um iterador. Então, iter1é um iterável e um iterador nesse caso?

Além disso, iter1é diferente do exemplo 1 acima, porque o iterável no exemplo 1 pode fornecer [1, 3, 5]quantas vezes quiser [...bar], enquanto iter1é iterável, mas como ele retorna, que é o mesmo iterador todas as vezes, dará apenas [1, 3, 5]uma vez.

Então, podemos dizer, de forma iterável bar, quantas vezes podem [...bar]dar o resultado [1, 3, 5]- e a resposta é: depende. E é iterável o mesmo que um iterador? E a resposta é: são coisas diferentes, mas podem ser as mesmas quando o iterável se usa como iterador. Isso está correto?



" Então, iter1é um iterável e um iterador neste caso? " - sim. Todos os iteradores nativos também são iteráveis ​​retornando a si mesmos, para que você possa transmiti-los facilmente para construções que esperam ser iteráveis.
Bergi 23/12/19

Respostas:


10

Sim, iterables e iteradores são coisas diferentes, mas a maioria dos iteradores (incluindo todas as que você recebe do próprio JavaScript, como a dos keysou valuesmétodos em Array.prototypeou geradores de funções de gerador) herdam a % IteratorPrototype% objeto , que tem um Symbol.iteratormétodo como isto:

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

O resultado é que todos os iteradores padrão também são iteráveis. É assim que você pode usá-los diretamente ou usá-los em for-ofloops e outros (que esperam iteráveis, não iteradores).

Considere o keysmétodo de matrizes: ele retorna um iterador de matriz que visita as chaves da matriz (seus índices, como números). Observe que ele retorna um iterador . Mas um uso comum é:

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

for-ofleva um iterável , não um iterador , então por que isso funciona?

Funciona porque o iterador também é iterável; Symbol.iteratorapenas retorna this.

Aqui está um exemplo que eu uso no capítulo 6 do meu livro: se você quiser fazer um loop sobre todas as entradas, mas pular a primeira e não desejar slicecortar o subconjunto, poderá obter o iterador, ler o primeiro valor, então entregue a um for-ofloop:

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

Observe que isso são todos os iteradores padrão . Em algum momento, as pessoas mostram exemplos de iteradores codificados manualmente como este:

O iterador retornado por rangelá é não um iterável, por isso falhar quando tentamos usá-lo com for-of.

Para torná-lo iterável, precisamos:

  1. Adicione o Symbol.iteratormétodo no início da resposta acima a ele ou
  2. Herde-o de% IteratorPrototype%, que já possui esse método

Infelizmente, o TC39 decidiu não fornecer uma maneira direta de obter o objeto% IteratorPrototype%. Existe uma maneira indireta (obter um iterador de uma matriz e, em seguida, pegar seu protótipo, que é definido como% IteratorPrototype%), mas é uma dor.

Mas não há necessidade de escrever iteradores manualmente assim; basta usar uma função de gerador, pois o gerador que ele retorna é iterável:


Por outro lado, nem todos os iteráveis ​​são iteradores. Matrizes são iteráveis, mas não iteradoras. O mesmo acontece com seqüências de caracteres, mapas e conjuntos.


0

Descobri que existem algumas definições mais precisas dos termos e estas são as respostas mais definitivas:

De acordo com as especificações ES6 e MDN :

Quando temos

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

fooé chamado de função de gerador . E então quando tivermos

let bar = foo();

baré um objeto gerador . E um objeto gerador está em conformidade com o protocolo iterável e o protocolo iterador .

A versão mais simples é a interface do iterador, que é apenas um .next()método.

O protocolo iterável é: para o objeto obj, obj[Symbol.iterator]fornece uma "função de zero argumentos que retorna um objeto, em conformidade com o protocolo do iterador".

Pelo título do link MDN , também parece que também podemos chamar um objeto gerador de "gerador".

Observe que no livro de Nicolas Zakas, Entendendo o ECMAScript 6 , ele provavelmente chamou vagamente uma "função geradora" como "gerador" e um "objeto gerador" como "iterador". O ponto de partida é que ambos são realmente "geradores" - um é uma função de gerador e outro é um objeto gerador, ou gerador. O objeto gerador está em conformidade com o protocolo iterável e o protocolo iterador.

Se for apenas um objeto em conformidade com o protocolo do iterador , você não poderá usar [...iter]ou for (a of iter). Tem que ser um objeto que esteja em conformidade com o protocolo iterável .

E também há uma nova classe Iterator, em futuras especificações de JavaScript que ainda estão em rascunho . Ele tem uma interface maior, incluindo métodos tais como forEach, map, reduceda interface matriz atual, e os novos, como e take, e drop. O iterador atual refere-se ao objeto apenas com a nextinterface.

Para responder à pergunta original: qual é a diferença entre um iterador e um iterável, a resposta é: um iterador é um objeto com a interface .next()e um iterável é um objeto objque obj[Symbol.iterator]pode fornecer uma função de argumento zero que, quando invocada, retorna um iterador.

E um gerador é um iterável e um iterador, para adicionar a isso.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.