javascript-iterables-e-iteradores

Qué son los Iterables e Iteradores en JavaScript

En JavaScript, los iterables son objetos que nos permiten recorrer y acceder a los elementos de una colección de datos de manera secuencial y sencilla.

En términos simples, iterables e iteradores son una herramienta que nos permite “iterar” sobre los valores de una colección.

A diferencia de los métodos tradicionales de acceso directo a los elementos (como los índices en los arrays), los iteradores permiten recorrer colecciones sin necesidad de tener acceso directo a la estructura interna.

Los iterables pueden ser recorridos de manera secuencial utilizando un bucle for...of u otras funciones que trabajan con iterables, como map(), filter(), reduce(), entre otros.

Como funcionan los iterables

Bajo el capó, un iterable es cualquier objeto que implementa el método Symbol.iterator, el cual devuelve un iterador.

Por su parte, un iterador es simplemente un objeto que implementa un método next(). Este método devuelve un objeto con dos propiedades:

  • value: El valor actual de la iteración.
  • done: Un valor booleano (true o false) que indica si la iteración ha finalizado.

Por ejemplo, al usar un iterador para recorrer un array, cada llamada a next() devolverá el siguiente valor del array hasta que se alcance el final.

En resumen,

  • Iterable: Un objeto que tiene un método Symbol.iterator que devuelve un iterador.
  • Iterador: Un objeto con el método next() que devuelve un objeto con las propiedades value y done.

Usando iteradores con bucles

Iteración manual con next()

Para utilizar un iterador, podemos llamarlo manualmente mediante el método next().

const iterable = [10, 20, 30];
const iterator = iterable[Symbol.iterator]();

let result = iterator.next();
while (!result.done) {
  console.log(result.value); // Imprime los valores 10, 20, 30
  result = iterator.next();
}

En este ejemplo,

  • Comenzamos la iteración con iterator.next()
  • Luego usamos un bucle while para continuar iterando hasta que se marque la propiedad done como true.

Uso de for...of

Una forma más sencilla y común de trabajar con iteradores es mediante el bucle for...of, que automáticamente maneja el proceso de iteración.

const iterable = [1, 2, 3, 4];

for (const value of iterable) {
  console.log(value); // Imprime 1, 2, 3, 4
}
  • Este bucle se encargará de llamar a next() repetidamente hasta que se haya recorrido todo el iterable
  • Internamente, el bucle utiliza el método next() del iterador.

Crear nuestros propios iteradores e iterables

También podemos crear nuestros propios iterables e iteradores. Esto es útil cuando necesitamos una forma personalizada de iterar sobre una colección de datos que no es estándar, o cuando estamos trabajando con estructuras de datos complejas.

Iterador custom

Por ejemplo, imaginemos que queremos crear un iterador para recorrer un objeto que contiene una lista de valores. Podemos hacerlo implementando el método next() dentro de un objeto:

const miIterador = {
  valores: [10, 20, 30],
  currentIndex: 0,
  next() {
    if (this.currentIndex < this.valores.length) {
      return { value: this.valores[this.currentIndex++], done: false };
    }
    return { value: undefined, done: true };
  }
};

console.log(miIterador.next()); // { value: 10, done: false }
console.log(miIterador.next()); // { value: 20, done: false }
console.log(miIterador.next()); // { value: 30, done: false }
console.log(miIterador.next()); // { value: undefined, done: true }

En este ejemplo,

  • Hemos creado un objeto miIterador que actúa como un iterador para un array de números.
  • La propiedad currentIndex lleva un seguimiento del índice actual en el array
  • El método next() devuelve el siguiente valor en la secuencia hasta que se alcance el final.

Iterable custom

Para crear un objeto iterable, debemos implementar el método Symbol.iterator, que es un tipo de propiedad de símbolo en JavaScript.

class CustomCollection {
    constructor(...items) {
        this.items = items;
    }

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

        return {
            next() {
                if (index < items.length) {
                    return { value: items[index++], done: false };
                } else {
                    return { value: undefined, done: true };
                }
            }
        };
    }
}

const collection = new CustomCollection('x', 'y', 'z');

for (const item of collection) {
    console.log(item);
}
// Salida:
// x
// y
// z

En este ejemplo,

  • CustomCollection es un objeto que implementa el método Symbol.iterator
  • Este devuelve un iterador con el método next() que retorna los valores de la propiedad items uno por uno hasta que se alcanza el final de la secuencia.

Utilizando generadores como iterables

También podemos utilizar generadores para crear objetos iterables de una manera más concisa y legible.

function* miGenerador() {
  yield 'Hola';
  yield 'Mundo';
}

const iterador = miGenerador();

for (const valor of iterador) {
  console.log(valor);
}

En este ejemplo, el generador miGenerador crea un objeto iterable que genera los valores “Hola” y “Mundo” cuando es recorrido con un bucle for...of.