En JavaScript ES6, ¿cuál es la diferencia entre un iterador y un iterador?


14

¿Un iterable es lo mismo que un iterador, o son diferentes?

Parece, según las especificaciones , un iterable es un objeto, por ejemplo, objtal que se obj[Symbol.iterator]refiere a una función, de modo que cuando se invoca, devuelve un objeto que tiene un nextmétodo que puede devolver un {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)

Entonces, en el código anterior, bares el iterable, y wahes el iterador, y next()es la interfaz del iterador.

Entonces, iterable e iterator son cosas diferentes.

Ahora, sin embargo, en un ejemplo común de generador 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

En el caso anterior, gen1es el generador, y iter1es el iterador, y iter1.next()hará el trabajo adecuado. Pero iter1[Symbol.iterator]da una función que, cuando se invoca, devuelve iter1, que es un iterador. Entonces, ¿ iter1es tanto iterable como iterador en este caso?

Además, iter1es diferente del ejemplo 1 anterior, porque el iterable en el ejemplo 1 puede dar [1, 3, 5]tantas veces como quiera usando [...bar], mientras que iter1es iterable, pero dado que regresa a sí mismo, que es el mismo iterador cada vez, solo dará [1, 3, 5]una vez.

Entonces, podemos decir, para un iterable bar, cuántas veces puede [...bar]dar el resultado [1, 3, 5], y la respuesta es que depende. ¿Y es iterable lo mismo que un iterador? Y la respuesta es que son cosas diferentes, pero pueden ser lo mismo, cuando el iterable se utiliza como iterador. ¿Es eso correcto?



" Entonces, ¿ iter1es tanto iterable como iterador en este caso? ", Sí. Todos los iteradores nativos también son iterables devolviéndose ellos mismos, para que pueda pasarlos fácilmente a construcciones que esperan un iterable.
Bergi el

Respuestas:


10

Sí, iterables y iteradores son cosas diferentes, pero la mayoría de los iteradores (incluyendo todos los que te dan de sí misma JavaScript, como de los keyso valuesmétodos en Array.prototypeo generadores de funciones del generador) heredan de la % objeto% IteratorPrototype , que cuenta con un Symbol.iteratormétodo como esta:

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

El resultado es que todos los iteradores estándar también son iterables. Eso es para que pueda usarlos directamente, o usarlos en for-ofbucles y demás (que esperan iterables, no iteradores).

Considere el keysmétodo de matrices: devuelve un iterador de matriz que visita las claves de la matriz (sus índices, como números). Tenga en cuenta que devuelve un iterador . Pero un uso común es:

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

for-oftoma un iterable , no un iterador , entonces, ¿por qué funciona eso?

Funciona porque el iterador también es iterable; Symbol.iteratorsolo vuelve this.

Aquí hay un ejemplo que uso en el Capítulo 6 de mi libro: si desea recorrer todas las entradas pero omite la primera y no desea utilizar slicepara cortar el subconjunto, puede obtener el iterador, leer el primer valor, luego pasar a un for-ofbucle:

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

Tenga en cuenta que esto es todo iteradores estándar . Algunas veces, las personas muestran ejemplos de iteradores codificados manualmente como este:

El iterador devuelto por rangeallí no es iterable, por lo que falla cuando intentamos usarlo con for-of.

Para hacerlo iterable, tendríamos que:

  1. Agregue el Symbol.iteratormétodo al comienzo de la respuesta anterior, o
  2. Haz que herede de% IteratorPrototype%, que ya tiene ese método

Lamentablemente, TC39 decidió no proporcionar una forma directa de obtener el objeto% IteratorPrototype%. Hay una forma indirecta (obtener un iterador de una matriz, luego tomar su prototipo, que se define como% IteratorPrototype%), pero es una molestia.

Pero de todos modos no hay necesidad de escribir iteradores de forma manual; solo use una función de generador, ya que el generador que devuelve es iterable:


En contraste, no todos los iterables son iteradores. Las matrices son iterables, pero no iteradores. También lo son las cadenas, los mapas y los conjuntos.


0

Descubrí que hay algunas definiciones más precisas de los términos, y estas son las respuestas más definitivas:

De acuerdo con las especificaciones ES6 y MDN :

Cuando nosotros tenemos

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

foose llama función generadora . Y luego cuando tenemos

let bar = foo();

barEs un objeto generador . Y un objeto generador se ajusta tanto al protocolo iterable como al protocolo iterador .

La versión más simple es la interfaz iteradora, que es solo un .next()método.

El protocolo iterable es: para el objeto obj, obj[Symbol.iterator]proporciona una "función de argumentos cero que devuelve un objeto, conforme al protocolo iterador".

Por el título del enlace MDN , también parece que también podemos llamar a un objeto generador un "generador".

Tenga en cuenta que en el libro de Nicolas Zakas, Understanding ECMAScript 6 , probablemente llamó libremente a una "función generadora" como "generador" y a un "objeto generador" como "iterador". El punto de partida es que ambos están realmente relacionados con el "generador": uno es una función generadora y el otro es un objeto generador o generador. El objeto generador se ajusta tanto al protocolo iterable como al protocolo iterador.

Si es solo un objeto conforme al protocolo iterador , no puede usar [...iter]o for (a of iter). Tiene que ser un objeto que se ajuste al protocolo iterable .

Y luego, también hay una nueva clase Iterator, en una futura especificación de JavaScript que todavía está en borrador . Tiene una interfaz más grande, incluyendo métodos tales como forEach, map, reducede la interfaz de matriz actual, y los nuevos, tales como y take, y drop. El iterador actual se refiere al objeto solo con la nextinterfaz.

Para responder a la pregunta original: cuál es la diferencia entre un iterador y un iterable, la respuesta es: un iterador es un objeto con la interfaz .next(), y un iterable es un objeto objque obj[Symbol.iterator]puede dar una función de argumento cero que, cuando se invoca, devuelve un iterador

Y un generador es tanto iterable como iterador, para agregar a eso.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.