Iterables e Iteradores en JavaScript
Aprende qué son los iterables en JavaScript, cómo funciona el protocolo de iterador, y cómo usar for...of, spread y generadores eficientemente.
Introducción a los Iterables en JavaScript
Los iterables de JavaScript son objetos que implementan un protocolo específico, lo que les permite ser consumidos por construcciones de iteración como for...of. Esta guía cubre los conceptos fundamentales, los iterables integrados y las técnicas prácticas para trabajar con colecciones.
¿Qué son los Iterables en JavaScript?
En esencia, un iterable es un objeto que implementa el método Symbol.iterator, lo que permite el acceso secuencial a sus elementos. Varios tipos integrados en JavaScript son iterables, incluidos Array, String, Map, Set y más. Es importante distinguir entre un iterable (el objeto que se recorre) y un iterador (el objeto devuelto por Symbol.iterator que realiza el recorrido). Estos iterables son fundamentales para diversas operaciones, como el bucle y la manipulación de datos.
Para una exploración detallada de los objetos Map y Set de JavaScript, consulta nuestra guía completa sobre JavaScript Map and Set.
Ejemplo de un Iterable: Array
Veamos un ejemplo básico de un iterable en JavaScript:
Este fragmento de código demuestra cómo iterar sobre un array de frutas, un iterable común. El bucle for...of llama automáticamente al método Symbol.iterator del iterable y consume el iterador resultante hasta que done es true. Consulta bucles de JavaScript para ver la familia completa de construcciones de bucle.
No confundas for...of con for...in. for...of itera sobre los valores producidos por un iterable (elementos de array, caracteres de string, entradas de Map). for...in itera sobre las claves de propiedades enumerables de un object — incluyendo las heredadas — y está pensado para objetos simples, no arrays. Usar for...in en un array te da strings de índice ("0", "1", …) y puede capturar propiedades adicionales, así que prefiere for...of para datos ordenados.
El Protocolo Iterador
La piedra angular de un iterable es su método Symbol.iterator. El protocolo es un contrato preciso:
- Un iterable tiene un método identificado por
Symbol.iterator. Al llamarlo, devuelve un iterador. - Un iterador es un object con un método
next(). - Cada llamada a
next()devuelve un object{ value, done }:done: false—valuees el siguiente elemento de la secuencia.done: true— la iteración ha terminado (valuese ignora en ese caso, o lleva un resultado final opcional).
Cualquier objeto que siga este contrato funciona con for...of, el operador spread, la desestructuración y Array.from() — incluso si lo has escrito tú mismo. Symbol es una clave única integrada; consulta tipo Symbol para entender por qué los métodos de protocolo usan uno en lugar de un nombre de string simple.
Ejemplo: Un iterable range personalizado
Un caso de uso clásico es un objeto que produce un rango numérico de forma perezosa, sin construir nunca un array:
En este ejemplo, el método [Symbol.iterator]() devuelve un iterador nuevo cada vez, por lo que el mismo objeto range puede recorrerse más de una vez. La sintaxis de método abreviado garantiza que this se refiera correctamente al objeto range. (Usar una función flecha para [Symbol.iterator] capturaría this léxicamente y rompería el patrón.)
Generadores: la forma fácil de construir iterables
Escribir next() y rastrear el estado manualmente es verboso. Una función generadora — declarada con function* y usando yield — produce un iterador automáticamente. Cada yield pausa la función y entrega un valor al consumidor; la ejecución se reanuda en la siguiente llamada a next(). El mismo range queda mucho más corto:
El * antes del nombre del método lo convierte en un método generador, por lo que range es iterable con casi ningún código repetitivo. Para todo lo que pueden hacer los generadores — incluyendo la comunicación bidireccional y la delegación con yield* — consulta Generadores y Iteradores y generadores asíncronos.
Secuencias infinitas y perezosas
Dado que un iterador solo calcula el siguiente valor cuando se le pide, puede describir secuencias que son demasiado grandes — o incluso infinitas — para almacenarlas en memoria. Esta es la razón principal para escribir un iterador personalizado en lugar de simplemente usar un array:
Nunca uses spread ([...naturals()]) ni for...of sin un break en un iterable infinito — se ejecutará para siempre. En su lugar, extrae un número finito de valores con next().
Consumir Iterables
Una vez que un objeto es iterable, todo el lenguaje se abre para él: cualquier construcción que acepte un iterable funciona con tu tipo personalizado de la misma manera que funciona con los arrays.
Usando Array.from()
El método Array.from() crea un nuevo array a partir de cualquier objeto iterable (o similar a un array). También acepta una función de mapeo opcional como segundo argumento, aplicada a cada elemento mientras se construye el array — más conveniente que Array.from(it).map(fn) porque evita un segundo recorrido:
Consulta JavaScript Map and Set para más información sobre Set, y métodos de Array para lo que puedes hacer una vez que tienes un array.
Sintaxis Spread con Iterables
La sintaxis spread (...) expande un iterable donde se esperan argumentos o elementos — fusionando arrays, copiando o pasando elementos como argumentos de función:
Para el panorama completo de ... tanto en posición spread como de recolección, consulta Parámetros rest y sintaxis spread.
Desestructuración y rest
La asignación por desestructuración extrae valores de cualquier iterable por posición, y el patrón rest (...) reúne lo que queda en un array:
Aprende más en Asignación por desestructuración.
El iterable String y la seguridad Unicode
Los strings son iterables, y crucialmente el iterador recorre puntos de código Unicode, no unidades de código de 16 bits. Eso significa que los caracteres de par sustituto (emoji, algunos scripts) se mantienen intactos — a diferencia de la indexación con [i] o los bucles for más antiguos sobre .length, que pueden dividirlos:
Siempre que necesites contar o dividir correctamente los caracteres visibles para el usuario, itera el string (o haz spread) en lugar de depender de .length. Consulta el capítulo Strings para más información.
Resumen
- Un objeto es iterable cuando tiene un método
[Symbol.iterator]()que devuelve un iterador — un object cuyonext()produce{ value, done }. - Recurre a un generador (
function*/yield) en lugar de escribirnext()a mano; es la forma más corta y menos propensa a errores de hacer algo iterable. - Usa
for...ofpara los valores de datos ordenados/iterables; usafor...insolo para las claves de objetos simples. - Una vez iterable, tu tipo se conecta a spread, desestructuración, rest,
Array.from(it, mapFn)y muchas APIs integradas de forma gratuita. - Los iteradores son perezosos, por lo que pueden modelar secuencias infinitas o enormes que un array nunca podría.
- Itera los strings (no los indexas) para manejar caracteres Unicode como los emoji de forma segura.