Introducción a los Iteradores y Bucles for...of en ES6

En JavaScript ES5, la iteración sobre colecciones se realizaba comúnmente con bucles for tradicionales, el método forEach para arrays, y bucles for...in para objetos.


var vehiculos = {marca: "Ford", modelo: "Mustang", anio: 1969};
for (var clave in vehiculos) {
   console.log(clave); // marca, modelo, anio
   console.log(vehiculos[clave]); // Ford, Mustang, 1969
}
   

ES6 introdujo estruccturas de datos como Map y Set. Los iteradores proporcionan un mecanismo unificado para recorrer estas y otras colecciones. Un iterador es esencialmente una interfaz que, si una estructura de datos la implementa, permite su iteración. El bucle for...of, introducido en ES6, consume esta interfaz de iterador.

Un iterador es un objeto que implementa el método next(). Cada llamada a next() devuelve un objeto con dos propiedades: value, que contiene el valor actual, y done, un booleano que indica si la iteración ha finalizado (true si ha terminado, false en caso contrario). Si done es true, las llamadas posteriores a next() no tendrán efecto.

Una estructura de datos se considera "iterable" si posee una propiedad cuyo valor es un método que expone la interfaz del iterador, identificada por el símbolo Symbol.iterator.


const colores = ["rojo", "verde", "azul"];
const iteradorColores = colores[Symbol.iterator]();

console.log(iteradorColores.next()); // { value: "rojo", done: false }
console.log(iteradorColores.next()); // { value: "verde", done: false }
console.log(iteradorColores.next()); // { value: "azul", done: false }
console.log(iteradorColores.next()); // { value: undefined, done: true }
   

Se puede observar el comportamiento de iteración sobre un Map:


const mapaFrutas = new Map([["manzana", 1], ["banana", 2]]);
const iteradorMapa = mapaFrutas.keys();
let resultadoIteracion;

do {
   resultadoIteracion = iteradorMapa.next();
   console.log(resultadoIteracion.value); // manzana, banana
} while (!resultadoIteracion.done);
   

Estructuras de Datos Iterables

Las siguientes estructuras de datos son inherentemente iterables en JavaScript:

  • Array
  • Map
  • Set
  • String
  • TypedArray (arrays de tipos específicos, como Uint8Array)
  • El objeto arguments dentro de funciones
  • Objetos NodeList (obtenidos, por ejemplo, con document.querySelectorAll)

Nota: Los objetos literales estándar no son iterables por defecto. Sin embargo, es posible hacerlos iterables implementando la interfaz Symbol.iterator, como se muestra en el siguiente ejemplo:


function NodoLista(valorInicial) {
   this.valor = valorInicial;
   this.siguiente = null;
}

NodoLista.prototype[Symbol.iterator] = function() {
   const iterador = {
       siguiente: () => {
           if (nodoActual) {
               const valorActual = nodoActual.valor;
               nodoActual = nodoActual.siguiente;
               return { done: false, value: valorActual };
           } else {
               return { done: true, value: undefined };
           }
       }
   };
   let nodoActual = this;
   return iterador;
};

const nodo1 = new NodoLista(10);
const nodo2 = new NodoLista(20);
const nodo3 = new NodoLista(30);

nodo1.siguiente = nodo2;
nodo2.siguiente = nodo3;

for (const valor of nodo1) {
   console.log(valor); // 10, 20, 30
}
   

Contextos de Uso de la Interfaz Iterator

  1. Asignación por Desestructuración:

const conjunto = new Set().add('a').add('b').add('c');
const [primero, segundo] = conjunto; // primero = 'a', segundo = 'b'
const [elementoPrincipal, ...restoElementos] = conjunto; // elementoPrincipal = 'a', restoElementos = ['b', 'c']
       
  1. Operador de Propagación (Spread Operator):

// Ejemplo 1: Propagación de un string
const cadena = 'hola';
const arrayCaracteres = [...cadena]; // ['h', 'o', 'l', 'a']

// Ejemplo 2: Propagación en un array literal
let letras = ['b', 'c'];
let nuevaPalabra = ['a', ...letras, 'd']; // ['a', 'b', 'c', 'd']
       
  1. Expresión yield* en Funciones Generadoras:

function* generadorSecuencial() {
 yield 1;
 yield* [2, 3, 4]; // Itera sobre el array [2, 3, 4]
 yield 5;
}

const iteradorGenerador = generadorSecuencial();
console.log(iteradorGenerador.next()); // { value: 1, done: false }
console.log(iteradorGenerador.next()); // { value: 2, done: false }
console.log(iteradorGenerador.next()); // { value: 3, done: false }
console.log(iteradorGenerador.next()); // { value: 4, done: false }
console.log(iteradorGenerador.next()); // { value: 5, done: false }
console.log(iteradorGenerador.next()); // { value: undefined, done: true }
       
  1. Otros Contextos:
  • Bucles for...of
  • Array.from()
  • Constructores Map(), Set(), WeakMap(), WeakSet()
  • Promise.all()
  • Promise.race()

Ventajas del Bucle for...of

A diferencia del método forEach de los arrays, el bucle for...of permite el uso de sentencias como break y continue para controlar el flujo de la iteración, además de return para salir de la función contenedora.

Los bucles for...in, diseñados para objetos, presentan inconvenientes al usarse con arrays:

  • Iteran sobre las claves como strings (ej. "0", "1"), no como números.
  • Pueden iterar sobre propiedades añadidas manualmente o incluso heredadas del prototipo.
  • El orden de iteración no siempre está garantizado.

Las ventajas clave de for...of son:

  • Sintaxis clara y concisa, similar a for...in pero sin sus desventuras.
  • Compatibilidad con break, continue y return.
  • Proporciona una interfaz de iteración uniforme para diversas estructuras de datos.

1. Arrays: for...of itera sobre los valores de los índices numéricos de un array, ignorando propiedaeds adicionales como foo en el siguiente ejemplo:


const miArray = [10, 20, 30];
miArray.propiedadExtra = 'valor';

console.log("Iteración con for...in:");
for (const indice in miArray) {
   console.log(`Índice: ${indice}, Valor: ${miArray[indice]}`);
   // Índices: "0", "1", "2", "propiedadExtra"
   // Valores: "10", "20", "30", "valor"
}

console.log("\nIteración con for...of:");
for (const valor of miArray) {
   console.log(valor); // 10, 20, 30
}
   

2. Map y Set: La iteración sobre Map y Set sigue el orden de inserción. Al iterar sobre un Set se obtienen los valores directamente. Al iterar sobre un Map, se obtienen pares clave-valor en forma de arrays ([clave, valor]).

3. Estructuras Pseudo-Arrays:

  • Strings: Los strings son iterables, y for...of itera sobre sus caracteres. Maneja correctamente los caracteres Unicode que ocupan más de una unidad de código UTF-16.

// Iteración estándar de un string
let saludo = "mundo";
for (const letra of saludo) {
  console.log(letra); // m, u, n, d, o
}

// Iteración de un string con caracteres Unicode complejos
for (const char of 'a\uD83D\uDC0A') {
  console.log(char); // 'a', '\uD83D\uDC0A' (representa un emoji)
}
        
  1. Objetos NodeList: Permite iterar sobre los nodos de una lista, útil para aplicar estilos o manipulaciones.

const parrafos = document.querySelectorAll("p");
for (const parrafo of parrafos) {
 parrafo.style.fontWeight = "bold";
}
       
  1. Objeto arguments: Dentro de una función, arguments se puede iterar con for...of.

function mostrarArgumentos() {
 for (const arg of arguments) {
   console.log(arg);
 }
}
mostrarArgumentos("uno", "dos", "tres"); // uno, dos, tres
       

Manejo de Pseudo-Arrays sin Interfaz Iterator: Para estructuras pseudo-arrays que no implementan Symbol.iterator (como objetos con una propiedad length y claves numéricas), se puede usar Array.from() para convertirlas en arrays iterables.


const objetoLikeArray = {
   length: 3,
   0: 'primero',
   1: 'segundo',
   2: 'tercero'
};

// Esto generaría un error:
// for (const item of objetoLikeArray) { console.log(item); }

// Uso correcto con Array.from():
for (const item of Array.from(objetoLikeArray)) {
   console.log(item); // primero, segundo, tercero
}
   

Ejemplos de Aplicación de Iteradores

  1. Generador de la Secuencia de Fibonacci:

const fibonacci = {
   [Symbol.iterator]() {
       return this; // El objeto en sí es el iterador
   },
   anterior: 0,
   actual: 1,
   next() {
       const valorActual = this.anterior;
       [this.anterior, this.actual] = [this.actual, this.anterior + this.actual];
       return { value: valorActual, done: false };
   }
};

// Imprimir los primeros 10 números de Fibonacci
for (let i = 0; i < 10; i++) {
   console.log(fibonacci.next().value);
}
// Salida: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34
       
  1. Iterador para una Cola de Tareas: (Conceptual) Se podría implementar un iterador que recorra una cola de tareas pendientes.
  2. Ejecución Retardada: (Conceptual) Un iterador podría ser diseñado para ejecutar acciones con cierto retardo entre cada paso.

Etiquetas: JavaScript ES6 iteradores for...of Map

Publicado el 7-8 03:41