Iterables e iteradores: una guía detallada de JavaScript



Este artículo es una introducción en profundidad a los iterables y los iteradores en JavaScript. Mi principal motivación para escribirlo fue prepararme para aprender sobre generadores. De hecho, estaba planeando experimentar más tarde combinando generadores y anzuelos React. Si estás interesado, ¡sigue mi Twitter o YouTube !



En realidad, planeé comenzar con un artículo sobre generadores, pero pronto se hizo evidente que es difícil hablar de ellos sin una buena comprensión de los iterables y los iteradores. Nos centraremos en ellos ahora. Asumiré que no sabes nada sobre este tema, pero al mismo tiempo profundizaremos en él de manera significativa. Entonces si eres algo conozca los iterables y los iteradores, pero no se sienta cómodo usándolos, este artículo lo ayudará.



Introducción



Como notó, estamos discutiendo iterables e iteradores. Estos son conceptos interrelacionados, pero diferentes, por lo que al leer el artículo, preste atención a cuál se está discutiendo en un caso particular.



Comencemos con los objetos iterables. ¿Lo que es? Esto es algo que se puede iterar, por ejemplo:



for (let element of iterable) {
    // do something with an element
}

      
      





Tenga en cuenta que aquí solo estamos viendo los bucles for ... of



que se introdujeron en ES6. Y los bucles for ... in



son una construcción más antigua a la que no nos referiremos en absoluto en este artículo.



Ahora podría estar pensando: "¡Está bien, esta variable iterable es solo una matriz!" Así es, las matrices son iterables. Pero ahora hay otras estructuras en JavaScript nativo que puede usar en un bucle for ... of



. Es decir, además de las matrices, hay otros objetos iterables.



Por ejemplo, podemos iterar Map



, introducido en ES6:



const ourMap = new Map();

ourMap.set(1, 'a');
ourMap.set(2, 'b');
ourMap.set(3, 'c');

for (let element of ourMap) {
    console.log(element);
}

      
      





Este código mostrará:



[1, 'a']
[2, 'b']
[3, 'c']

      
      





Es decir, la variable element



en cada etapa de iteración almacena una matriz de dos elementos. La primera es la clave, la segunda es el valor.



El hecho de que pudiéramos usar un bucle for ... of



para iterar Map



demuestra que Map



son iterables. Nuevamente for ... of



, solo se pueden usar objetos iterables en bucles . Es decir, si algo funciona con este bucle, entonces es un objeto iterable.



Es curioso que el constructor Map



acepte opcionalmente iterables de pares clave-valor. Es decir, esta es una forma alternativa de construir el mismo Map



:



const ourMap = new Map([
    [1, 'a'],
    [2, 'b'],
    [3, 'c'],
]);

      
      





Y como Map



es un iterable, podemos hacer copias de él muy fácilmente:



const copyOfOurMap = new Map(ourMap);

      
      





Ahora tenemos dos diferentes Map



, aunque almacenan las mismas claves con los mismos valores.



Entonces vimos dos ejemplos de objetos iterables: matriz y ES6 Map



. Pero aún no sabemos cómo consiguieron la capacidad de ser iterables. La respuesta es simple: hay iteradores asociados con ellos . Tenga cuidado: los iteradores no son iterables .



¿Cómo se asocia un iterador con un objeto iterable? Un objeto simplemente iterable debe contener una función en su propiedad Symbol.iterator



. Cuando se llama, la función debe devolver un iterador para este objeto.



Por ejemplo, puede recuperar un iterador de matriz:



const ourArray = [1, 2, 3];

const iterator = ourArray[Symbol.iterator]();

console.log(iterator);

      
      





Este código se envía a la consola Object [Array Iterator] {}



. Ahora sabemos que la matriz tiene un iterador asociado, que es algún tipo de objeto.



¿Qué es un iterador?



Es simple. Un iterador es un objeto que contiene un método next



. Cuando se llama a este método, debería devolver:



  • siguiente valor en una secuencia de valores;
  • información sobre si el iterador ha terminado de generar valores.


Probemos esto llamando a un método next



en nuestro iterador de matriz:



const result = iterator.next();

console.log(result);

      
      





Veremos el objeto en la consola { value: 1, done: false }



. El primer elemento de la matriz que creamos es 1, y aquí apareció como un valor. También recibimos información de que el iterador aún no ha terminado, es decir, aún podemos llamar a la función next



y obtener algunos valores. ¡Intentemos! Llamémoslo next



dos veces más:



console.log(iterator.next());
console.log(iterator.next());

      
      





Recibido uno por uno { value: 2, done: false }



y { value: 3, done: false }



.



Solo hay tres elementos en nuestra matriz. ¿Qué pasa si lo vuelves a llamar next



?



console.log(iterator.next());

      
      





Esta vez veremos { value: undefined, done: true }



. Esto indica que el iterador está completo. No tiene sentido volver a llamar next



. Si hacemos esto, una y otra vez recibiremos un objeto { value: undefined, done: true }



. done: true



significa dejar de iterar.



Ahora puedes entender lo que hace for ... of



bajo el capó:



  • [Symbol.iterator]()



    se llama al primer método para obtener el iterador;
  • el método next



    se llama cíclicamente en el iterador hasta que lo obtenemos done: true



    ;
  • después de cada llamada next



    , la propiedad se usa en el cuerpo del ciclo value



    .


Escribamos todo esto en código:



const iterator = ourArray[Symbol.iterator]();

let result = iterator.next();

while (!result.done) {
    const element = result.value;

    // do some something with element

    result = iterator.next();
}

      
      





Este código es equivalente a esto:



for (let element of ourArray) {
    // do something with element
}

      
      





Puede verificar esto, por ejemplo, insertando en console.log(element)



lugar de un comentario // do something with element



.



Crea tu propio iterador



Ahora sabemos qué son los iterables y los iteradores. Surge la pregunta: "¿Puedo escribir mis propias instancias?"



¡Ciertamente!



Los iteradores no tienen nada de misterioso. Estos son solo objetos con un método next



que se comportan de una manera especial. Ya hemos descubierto qué valores nativos en JS son iterables. No se mencionaron objetos entre ellos. De hecho, no se repiten de forma nativa. Considere un objeto como este:



const ourObject = {
    1: 'a',
    2: 'b',
    3: 'c'
};

      
      





Si iteramos con él for (let element of ourObject)



, obtenemos un error object is not iterable



.



¡Escribamos nuestros propios iteradores haciendo que dicho objeto sea iterable!



Para hacer esto, debe parchear el prototipo Object



con su propio método [Symbol.iterator]()



. Dado que parchear el prototipo es una mala práctica, creemos nuestra propia clase ampliando Object



:



class IterableObject extends Object {
    constructor(object) {
        super();
        Object.assign(this, object);
    }
}

      
      





El constructor de nuestra clase toma un objeto ordinario y copia sus propiedades en un objeto iterable (¡aunque todavía no es iterable!).



Creemos un objeto iterable:



const iterableObject = new IterableObject({
    1: 'a',
    2: 'b',
    3: 'c'
})

      
      





Para que una clase sea IterableObject



verdaderamente iterable, necesitamos un método [Symbol.iterator]()



. Vamos a agregarlo.



class IterableObject extends Object {
    constructor(object) {
        super();
        Object.assign(this, object);
    }

    [Symbol.iterator]() {

    }
}

      
      





¡Ahora puedes escribir un iterador real!



Ya sabemos que debe ser un objeto con un método next



. Empecemos por esto.



class IterableObject extends Object {
    // same as before

    [Symbol.iterator]() {
        return {
            next() {}
        }
    }
}

      
      





Después de cada llamada, next



debe devolver un objeto de vista { value, done }



. Hagámoslo con valores ficticios.



class IterableObject extends Object {
    // same as before

    [Symbol.iterator]() {
        return {
            next() {
                return {
                    value: undefined,
                    done: false
                }
            }
        }
    }
}

      
      





Dado un objeto iterable como este:



const iterableObject = new IterableObject({
    1: 'a',
    2: 'b',
    3: 'c'
})

      
      





generaremos pares clave-valor, similar a cómo lo hace la iteración ES6 Map



:



['1', 'a']
['2', 'b']
['3', 'c']

      
      





En nuestro iterador, property



almacenaremos una matriz en el valor [key, valueForThatKey]



. Tenga en cuenta que esta es nuestra propia solución en comparación con los pasos anteriores. Si quisiéramos escribir un iterador que devuelva solo claves o solo valores de propiedad, entonces podríamos hacerlo sin ningún problema. Decidimos devolver pares clave-valor ahora.



Necesitamos una matriz del tipo [key, valueForThatKey]



. La forma más sencilla de conseguirlo es con el método Object.entries



. Podemos usarlo justo antes de crear el objeto iterador en el método [Symbol.iterator]()



:



class IterableObject extends Object {
    // same as before

    [Symbol.iterator]() {
        // we made an addition here
        const entries = Object.entries(this);

        return {
            next() {
                return {
                    value: undefined,
                    done: false
                }
            }
        }
    }
}

      
      





El iterador devuelto en el método accederá a la variable gracias al cierre de JavaScript entries



.



También necesitamos una variable de estado. Nos dirá qué par clave-valor debe devolverse en la próxima llamada next



. Vamos a agregarlo:



class IterableObject extends Object {
    // same as before

    [Symbol.iterator]() {
        const entries = Object.entries(this);
        // we made an addition here
        let index = 0;

        return {
            next() {
                return {
                    value: undefined,
                    done: false
                }
            }
        }
    }
}

      
      





Tenga en cuenta que declaramos la variable index



c let



porque sabemos que planeamos actualizar su valor después de cada llamada next



.



Ahora estamos listos para devolver el valor real en el método next



:



class IterableObject extends Object {
    // same as before

    [Symbol.iterator]() {
        const entries = Object.entries(this);
        let index = 0;

        return {
            next() {
                return {
                    // we made a change here
                    value: entries[index],
                    done: false
                }
            }
        }
    }
}

      
      





Fue fácil. Solo usamos variables entries



y index



para acceder al par clave-valor correcto de la matriz entries



.



Ahora tenemos que ocuparnos de la propiedad done



, porque ahora siempre lo será false



. Puede crear una variable más además de entries



y index



, y actualizarla después de cada llamada next



. Pero hay una forma aún más sencilla. Comprobemos si index



la matriz está fuera de los límites entries



:



class IterableObject extends Object {
    // same as before

    [Symbol.iterator]() {
        const entries = Object.entries(this);
        let index = 0;

        return {
            next() {
                return {
                    value: entries[index],
                    // we made a change here
                    done: index >= entries.length
                }
            }
        }
    }
}

      
      





Nuestro iterador termina cuando la variable index



es igual o mayor que su longitud entries



. Por ejemplo, si y tiene una entries



longitud de 3, entonces contiene valores en los índices 0, 1 y 2. Y cuando la variable index



es igual o mayor que 3, significa que no quedan más valores. Terminamos.



Este código casi funciona. Solo queda una cosa por agregar.



La variable index



comienza en 0, pero ... ¡no la actualizamos! No es tan simple. Necesitamos actualizar la variable después de haber regresado { value, done }



. Pero cuando lo devolvimos, el método next



se detiene inmediatamente incluso si hay algún código después de la expresión return



. Pero podemos crear un objeto { value, done }



, almacenarlo en una variable, actualizarlo index



y solo entonces devolver el objeto:



class IterableObject extends Object {
    // same as before

    [Symbol.iterator]() {
        const entries = Object.entries(this);
        let index = 0;

        return {
            next() {
                const result = {
                    value: entries[index],
                    done: index >= entries.length
                };

                index++;

                return result;
            }
        }
    }
}

      
      





Después de nuestros cambios, la clase se IterableObject



ve así:



class IterableObject extends Object {
    constructor(object) {
        super();
        Object.assign(this, object);
    }

    [Symbol.iterator]() {
        const entries = Object.entries(this);
        let index = 0;

        return {
            next() {
                const result = {
                    value: entries[index],
                    done: index >= entries.length
                };

                index++;

                return result;
            }
        }
    }
}

      
      





El código funciona muy bien, pero se volvió bastante confuso. Esto se debe a que muestra una forma más inteligente pero menos obvia de actualizar index



después de la creación del objeto result



. ¡Podemos simplemente inicializar index



a -1! Y aunque se actualiza antes de devolver el objeto desde next



, todo funcionará bien, porque la primera actualización reemplazará -1 con 0.



Así que hagámoslo:



class IterableObject extends Object {
    // same as before

    [Symbol.iterator]() {
        const entries = Object.entries(this);
        let index = -1;

        return {
            next() {
                index++;

                return {
                    value: entries[index],
                    done: index >= entries.length
                }
            }
        }
    }
}

      
      





Como puede ver, ahora no necesitamos hacer malabarismos con el orden de creación result



y actualización de objetos index



. Durante la segunda llamada, se index



actualizará a 1, y devolveremos un resultado diferente, y así sucesivamente. Todo funciona como queríamos y el código parece mucho más simple.



Pero, ¿cómo comprobamos la corrección del trabajo? Puede ejecutar manualmente un método [Symbol.iterator]()



para crear una instancia de un iterador y luego verificar directamente los resultados de las llamadas next



. ¡Pero puedes hacerlo mucho más fácil! Se dijo anteriormente que cualquier objeto iterable se puede insertar en un bucle for ... of



. Hagamos precisamente eso, registrando los valores devueltos por nuestro objeto iterable en el camino:



const iterableObject = new IterableObject({
    1: 'a',
    2: 'b',
    3: 'c'
});

for (let element of iterableObject) {
    console.log(element);
}

      
      





¡Trabajos! Esto es lo que se muestra en la consola:



[ '1', 'a' ]
[ '2', 'b' ]
[ '3', 'c' ]

      
      





¡Frio! Comenzamos con un objeto que no se podía usar en bucles for ... of



, porque no contienen de forma nativa iteradores integrados. Pero creamos el nuestro IterableObject



, que tiene un iterador autoescrito asociado.



Espero que ahora pueda ver el potencial de los iterables y los iteradores. Es un mecanismo que le permite crear sus propias estructuras de datos para trabajar con funciones JS como bucles for ... of



, ¡y funcionan como estructuras nativas! Esta es una característica muy útil que puede simplificar enormemente su código en ciertas situaciones, especialmente si planea iterar sus estructuras de datos con frecuencia.



Además, podemos personalizar qué deberían devolver exactamente estas iteraciones. Nuestro iterador ahora devuelve pares clave-valor. ¿Y si solo queremos valores? Fácil, simplemente reescribe el iterador:



class IterableObject extends Object {
    // same as before

    [Symbol.iterator]() {
        // changed `entries` to `values`
        const values = Object.values(this);
        let index = -1;

        return {
            next() {
                index++;

                return {
                    // changed `entries` to `values`
                    value: values[index],
                    // changed `entries` to `values`
                    done: index >= values.length
                }
            }
        }
    }
}

      
      





¡Y eso es! Si ahora iniciamos el bucle for ... of



, veremos en la consola:



a
b
c

      
      





Devolvimos solo los valores de los objetos. Todo esto demuestra la flexibilidad de los iteradores escritos por ellos mismos. Puedes hacer que te devuelvan lo que quieras.



Iteradores como ... objetos iterables



Es muy común que las personas confundan iteradores e iterables. Esto es un error y he tratado de separar cuidadosamente los dos. Sospecho que conozco la razón por la que la gente los confunde con tanta frecuencia.



Resulta que los iteradores ... ¡a veces son iterables!



¿Qué significa esto? Como recordará, un iterable es el objeto con el que está asociado un iterador. ¡Cada iterador de JavaScript nativo tiene un método [Symbol.iterator]()



que devuelve otro iterador! Esto hace que el primer iterador sea un objeto iterable.



Puede verificar esto tomando un iterador devuelto de una matriz e invocándolo [Symbol.iterator]()



:



const ourArray = [1, 2, 3];

const iterator = ourArray[Symbol.iterator]();

const secondIterator = iterator[Symbol.iterator]();

console.log(secondIterator);

      
      





Después de ejecutar este código, verá Object [Array Iterator] {}



. Es decir, un iterador no solo contiene otro iterador asociado a él, también es una matriz.



Si compara ambos iteradores con using, ===,



resulta que son exactamente iguales:



const iterator = ourArray[Symbol.iterator]();

const secondIterator = iterator[Symbol.iterator]();

// logs `true`
console.log(iterator === secondIterator);

      
      





Al principio, puede resultarle extraño el comportamiento de un iterador que es su propio iterador. Pero esta es una característica muy útil. No puede colocar un iterador desnudo en un bucle for ... of



, solo acepta un objeto iterable: un objeto con un método [Symbol.iterator]()



.



Sin embargo, la situación en la que un iterador es su propio iterador (y, por lo tanto, un objeto iterable) oculta el problema. Dado que los iteradores JS nativos contienen métodos [Symbol.iterator]()



, puede pasarlos directamente a bucles sin dudarlo for ... of



.



Como resultado, este fragmento:



const ourArray = [1, 2, 3];

for (let element of ourArray) {
    console.log(element);
}

      
      





y éste:



const ourArray = [1, 2, 3];
const iterator = ourArray[Symbol.iterator]();

for (let element of iterator) {
    console.log(element);
}

      
      





trabajar sin problemas y hacer lo mismo. Pero, ¿por qué alguien usaría iteradores como este en bucles directamente for ... of



? A veces es simplemente inevitable.



En primer lugar, es posible que deba crear un iterador sin pertenecer a ningún iterable. Veremos este ejemplo a continuación, y no es raro. A veces simplemente no necesitamos el iterable en sí.



Y sería muy incómodo si tener un iterador simple significara que no puede usarlo for ... of



. Por supuesto, puedes hacer esto manualmente usando un método next



y, por ejemplo, un bucle while



, pero hemos visto que esto requiere escribir mucho código y repetitivo.



La solución es simple: si desea evitar el código repetitivo y usar un iterador en un bucle for ... of



, debe convertir el iterador en un objeto iterable.



Por otro lado, también obtenemos iteradores con bastante frecuencia de métodos distintos a [Symbol.iterator]()



. Por ejemplo, ES6 Map



contiene métodos entries



, values



y keys



. Todos devuelven iteradores.



Si los iteradores JS nativos no fueran también objetos iterables, no podría usar estos métodos directamente en bucles for ... of



, como este:



for (let element of map.entries()) {
    console.log(element);
}

for (let element of map.values()) {
    console.log(element);
}

for (let element of map.keys()) {
    console.log(element);
}

      
      





Este código funciona porque los iteradores devueltos por los métodos también son objetos iterables. De lo contrario, tendría que, por ejemplo, envolver el resultado de la llamada map.entries()



en algún objeto iterable estúpido. Afortunadamente, no es necesario que hagamos esto.



Se considera una buena práctica crear sus propios objetos iterables. Especialmente si se devuelven de métodos distintos a [Symbol.iterator]()



. Es fácil convertir un iterador en un objeto iterable. Déjame mostrarte un ejemplo de un iterador IterableObject



:



class IterableObject extends Object {
    // same as before

    [Symbol.iterator]() {
        // same as before

        return {
            next() {
                // same as before
            },

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

      
      





Hemos creado un método [Symbol.iterator]()



debajo del método next



. Hizo de este iterador su propio iterador simplemente regresando this



, lo que significa que regresa a sí mismo. Arriba, ya hemos visto cómo se comporta un iterador de matriz. Esto es suficiente para que nuestro iterador funcione en bucles for ... of



incluso directamente.



Estado del iterador



Ahora debería ser obvio que cada iterador tiene un estado asociado. Por ejemplo, en un iterador, IterableObject



almacenamos un estado, una variable index



, como cierre. Y lo actualizamos después de cada paso de iteración.



¿Qué sucede después de que se completa el proceso de iteración? El iterador se vuelve inútil y puede (¡debería!) Eliminarlo. Puede ver que esto está sucediendo incluso con el ejemplo de objetos JS nativos. Tomemos un iterador de matriz e intentemos ejecutarlo dos veces en un bucle for ... of



.



const ourArray = [1, 2, 3];

const iterator = ourArray[Symbol.iterator]();

for (let element of iterator) {
    console.log(element);
}

for (let element of iterator) {
    console.log(element);
}

      
      





Se podría esperar que la consola para mostrar los números en dos ocasiones 1



, 2



y 3



. Pero el resultado será así:



1
2
3

      
      





¿Por qué?



Llamemos manualmente next



después de que termine el ciclo:



const ourArray = [1, 2, 3];

const iterator = ourArray[Symbol.iterator]();

for (let element of iterator) {
    console.log(element);
}

console.log(iterator.next());

      
      





El último registro se envía a la consola { value: undefined, done: true }



.



Eso es. Una vez finalizado el ciclo, el iterador pasa al estado "hecho". Ahora siempre devolverá un objeto { value: undefined, done: true }



.



¿Hay alguna forma de "restablecer" el estado del iterador para que se pueda usar por segunda vez for ... of



? En algunos casos, es posible, pero no tiene sentido. Por tanto, [Symbol.iterator]



es un método, no solo una propiedad. Puede volver a llamar al método y obtener otro iterador:



const ourArray = [1, 2, 3];

const iterator = ourArray[Symbol.iterator]();

for (let element of iterator) {
    console.log(element);
}

const secondIterator = ourArray[Symbol.iterator]();

for (let element of secondIterator) {
    console.log(element);
}

      
      





Ahora todo funciona como se esperaba. Veamos por qué funcionan varios bucles hacia adelante a través de la matriz:



const ourArray = [1, 2, 3];

for (let element of ourArray) {
    console.log(element);
}

for (let element of ourArray) {
    console.log(element);
}

      
      





¡Todos los bucles for ... of



usan iteradores diferentes ! Una vez que el iterador y el bucle han terminado, este iterador ya no se usa.



Iteradores y matrices



Dado que usamos iteradores (aunque indirectamente) en bucles for ... of



, pueden verse engañosamente como matrices. Pero hay dos diferencias importantes. Iterator y Array utilizan los conceptos de valores codiciosos y vagos. Cuando crea una matriz, en un momento dado tiene una cierta longitud y sus valores ya están inicializados. Por supuesto, puede crear una matriz sin ningún valor, pero ese no es el caso. Mi punto es que no es posible crear una matriz que inicialice sus valores solo después de acceder a ellos escribiendo array[someIndex]



. Podría ser posible solucionar esto con un proxy o algún otro truco, pero por defecto, las matrices JavaScript no se comportan de esa manera.



Y cuando dicen que una matriz tiene una longitud, quieren decir que esta longitud es finita. No hay matrices infinitas en JavaScript.



Estas dos cualidades indican que las matrices son codiciosas .



Y los iteradores son vagos .



Para mostrar esto, crearemos dos de nuestros iteradores: el primero será infinito, a diferencia de los arreglos finitos, y el segundo inicializará sus valores solo cuando los solicite el usuario del iterador.



Comencemos con un iterador infinito. Suena intimidante, pero es muy sencillo de crear: el iterador comienza en 0 y devuelve el siguiente número de la secuencia en cada paso. Siempre.



const counterIterator = {
    integer: -1,

    next() {
        this.integer++;
        return { value: this.integer, done: false };
    },

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

      
      





¡Y eso es! Comenzamos con una propiedad de integer



-1. En cada llamada, next



lo incrementamos en 1 y lo devolvemos en el objeto como value



. Tenga en cuenta que se utilizó el truco mencionado de nuevo: empezamos a -1 para devolver 0 la primera vez.



También echar un vistazo a la propiedad done



. Se tendrá siempre ser falsa. ¡Este iterador no termina!



Además, hicimos que el iterador sea iterable dándole una implementación simple [Symbol.iterator]()



.



Una última cosa: este es el caso que mencioné anteriormente: creamos un iterador, pero no necesita un padre iterable para funcionar.



Ahora probemos este iterador en un bucle for ... of



. Solo debe recordar detener el bucle en algún momento, de lo contrario, el código se ejecutará para siempre.



for (let element of counterIterator) {
    if (element > 5) {
        break;
    }
    
    console.log(element);
}

      
      





Después del lanzamiento, veremos en la consola:



0
1
2
3
4
5

      
      





De hecho, hemos creado un iterador infinito que devuelve tantos números como desee. ¡Y fue muy fácil hacerlo!



Ahora escribamos un iterador que no cree valores hasta que se soliciten.



Bueno ... ¡ya lo hemos hecho!



¿Ha notado que counterIterator



solo se almacena un número de propiedad en un momento dado integer



? Este es el último número devuelto en la llamada next



. Y esta es la misma pereza. Un iterador puede potencialmente devolver cualquier número (más precisamente, un entero positivo). Pero solo los crea cuando son necesarios: cuando se llama al método next



.



Esto puede parecer bastante engañoso. Después de todo, los números se crean rápidamente y no ocupan mucho espacio en la memoria. Pero si está trabajando con objetos muy grandes que ocupan mucha memoria, a veces reemplazar matrices con iteradores puede ser muy útil, acelerando el programa y ahorrando memoria.



Cuanto más grande sea el objeto (o más tarde en crearse), mayor será el beneficio.



Otras formas de utilizar iteradores



Hasta ahora, solo hemos consumido iteradores en un bucle for ... of



o manualmente usando next



. Pero estas no son las únicas formas.



Ya hemos visto que el constructor Map



toma iterables como argumento. También puede Array.from



convertir fácilmente un iterable en una matriz utilizando el método . ¡Pero ten cuidado! Como dije, la pereza del iterador a veces puede ser una gran ventaja. La conversión a una matriz elimina la pereza. Todos los valores devueltos por el iterador se inicializan inmediatamente y luego se colocan en una matriz. Esto significa que si intentamos convertir infinito counterIterator



en una matriz, conducirá al desastre. Array.from



se ejecutará para siempre sin devolver un resultado. Entonces, antes de convertir un iterable / iterador en una matriz, debe asegurarse de que la operación sea segura.



Curiosamente, los iterables también funcionan bien con el operador de propagación (...



). Recuerde que esto funciona de la misma Array.from



manera cuando todos los valores del iterador se generan a la vez. Por ejemplo, puede crear su propia versión utilizando el operador de propagación Array.from



. Simplemente aplique el operador al iterable y luego coloque los valores en una matriz:



const arrayFromIterator = [...iterable];

      
      





También puede obtener todos los valores del iterable y aplicarlos a la función:



someFunction(...iterable);

      
      





Conclusión



Espero que ahora comprenda el título del artículo Objetos e iteradores iterables. Aprendimos qué son, en qué se diferencian, cómo usarlos y cómo crear nuestras propias instancias. Ahora estamos completamente preparados para trabajar con generadores. Si está familiarizado con los iteradores, pasar al siguiente tema debería ser sencillo.



All Articles