Entendiendo el patrón iterador (Iterator pattern) en Javascript
Feb 23, 2022
Updated: Jun 24, 2026

Entendiendo el patrón iterador (Iterator pattern) en Javascript

En este post aprenderemos cómo implementar el patrón iterador en JavaScript y en qué situaciones lo podemos usar.

El patrón iterador es uno de los patrones de diseño de software más usados en JavaScript y también uno de los más sencillos. Es un patrón de comportamiento, ya que define cómo se comunican los objetos entre sí. De él se extienden aplicaciones importantes que pueden ayudar a definir mejores arquitecturas en aplicaciones web, por lo que su uso y estudio es altamente recomendado.

Empecemos por definir este patrón. Básicamente todos hemos usado arrays de esta forma:

[1, 2, 3, 4]

Y normalmente, si queremos recorrerlo, nos inclinamos rápidamente por usar una estructura cíclica como for o while. Sin embargo, esto hace que el código sea un poco más imperativo y nos deja menos control sobre cada dato individual dentro del arreglo. Desde este punto es donde empieza a cobrar fuerza el patrón iterador, ya que nos provee una manera ordenada de recorrer la colección decidiendo nosotros cuándo queremos el siguiente objeto y cuándo no. Para ello deben existir obligatoriamente tres métodos dentro del iterador:

  • first() Retorna siempre el primer objeto de la colección.
  • next() Retorna el siguiente objeto de la colección si existe.
  • current() Retorna el objeto actual de la colección sobre el que estamos parados.

Empecemos entonces por definir una clase llamada Iterator con estos tres métodos:

// Define la clase "Iterator"
class Iterator {
  // Constructor de la clase "Iterator" que acepta un argumento "collection"
  constructor(collection) {
    // Inicializa la propiedad "index" con el valor 0
    this.index = 0;
    // Asigna el valor del argumento "collection" a la propiedad "collection" del objeto
    this.collection = collection;
  }
  // Método "first" para obtener el primer elemento de la colección
  first() {
    // Retorna el primer elemento de la colección (índice 0)
    return this.collection[0];
  }
  // Método "next" para obtener el siguiente elemento de la colección
  next() {
    // Incrementa el valor de la propiedad "index" en 1
    this.index += 1;
    // Retorna el elemento de la colección en la posición actual de "index"
    return this.collection[this.index];
  }
  // Método "current" para obtener el elemento actual de la colección
  current() {
    // Retorna el elemento de la colección en la posición actual de "index"
    return this.collection[this.index];
  }
}

Como ves, usando ES6 hemos definido una clase que permite hacer operaciones muy básicas sobre el array, pero conservando siempre cuál es el índice sobre el que está parada la colección en todo momento. Esto nos permitirá movernos sobre ella con facilidad. Adicionalmente, aunque no es obligatorio en la implementación de este patrón, sí es altamente recomendado añadir dos métodos utilitarios más sobre la clase iterador. Estos son:

  • hasNext() Retorna true si hay más elementos disponibles en la colección.
  • reset() Permite reiniciar el índice para iterar de nuevo sobre la colección.

Veamos entonces cómo sería la implementación:

// Define la clase "Iterator"
class Iterator {
  // ...

  // Método "reset" para reiniciar el índice del iterador al inicio de la colección
  reset() {
    // Asigna el valor 0 a la propiedad "index" del objeto
    this.index = 0;
  }

  // Método "hasNext" para verificar si hay un siguiente elemento en la colección
  hasNext() {
    // Retorna "true" si el índice actual más uno es menor que la longitud de la colección, de lo contrario retorna "false"
    return this.index + 1 < this.collection.length;
  }

  // ...
}

Súper simple. Una vez que tu clase esté definida, puedes usar prácticamente cualquier tipo de array para construir tu iterador y operar sobre él. Veamos un ejemplo de su uso práctico con un ciclo while:

// Crea una constante "arr" que contiene un array con los números del 1 al 5
const arr = [1, 2, 3, 4, 5];

// Crea una nueva instancia de la clase "Iterator" usando el array "arr" como argumento y asigna el objeto resultante a la constante "arrayIterator"
const arrayIterator = new Iterator(arr);

// Imprime en la consola el primer elemento del array usando el método "first" del objeto "arrayIterator"
console.log(arrayIterator.first());

// Inicia un bucle "while" que se ejecuta mientras haya un siguiente elemento en el array
while (arrayIterator.hasNext()) {
  // Imprime en la consola el siguiente elemento del array usando el método "next" del objeto "arrayIterator"
  console.log(arrayIterator.next());
}

De esta manera queda totalmente completo el patrón iterador. Una aclaración honesta: este ejemplo con while sigue siendo imperativo, nosotros controlamos el bucle a mano. Lo que el patrón nos da no es magia declarativa, sino una interfaz uniforme (first, next, current, hasNext) para recorrer cualquier colección sin que el código que la consume sepa cómo está construida por dentro. Más adelante veremos cómo JavaScript convierte esa misma idea en algo que sí se lee de forma declarativa con for...of.

A continuación puedes observar todo el código completo de este ejemplo:

// Define la clase "Iterator"
class Iterator {
  // Constructor de la clase "Iterator" que acepta un argumento "collection"
  constructor(collection) {
    // Inicializa la propiedad "index" con el valor 0
    this.index = 0;
    // Asigna el valor del argumento "collection" a la propiedad "collection" del objeto
    this.collection = collection;
  }
  // Método "first" para obtener el primer elemento de la colección
  first() {
    // Retorna el primer elemento de la colección (índice 0)
    return this.collection[0];
  }
  // Método "next" para obtener el siguiente elemento de la colección
  next() {
    // Incrementa el valor de la propiedad "index" en 1
    this.index += 1;
    // Retorna el elemento de la colección en la posición actual de "index"
    return this.collection[this.index];
  }
  // Método "current" para obtener el elemento actual de la colección
  current() {
    // Retorna el elemento de la colección en la posición actual de "index"
    return this.collection[this.index];
  }
  // Método "reset" para reiniciar el índice del iterador al inicio de la colección
  reset() {
    // Asigna el valor 0 a la propiedad "index" del objeto
    this.index = 0;
  }
  // Método "hasNext" para verificar si hay un siguiente elemento en la colección
  hasNext() {
    // Retorna "true" si el índice actual más uno es menor que la longitud de la colección, de lo contrario retorna "false"
    return this.index + 1 < this.collection.length;
  }
}

// Crea una constante "arr" que contiene un array con los números del 1 al 5
const arr = [1, 2, 3, 4, 5];

// Crea una nueva instancia de la clase "Iterator" usando el array "arr" como argumento y asigna el objeto resultante a la constante "arrayIterator"
const arrayIterator = new Iterator(arr);

// Imprime en la consola el primer elemento del array usando el método "first" del objeto "arrayIterator"
console.log(arrayIterator.first());

// Inicia un bucle "while" que se ejecuta mientras haya un siguiente elemento en el array
while (arrayIterator.hasNext()) {
  // Imprime en la consola el siguiente elemento del array usando el método "next" del objeto "arrayIterator"
  console.log(arrayIterator.next());
}

El patrón iterador ya vive dentro de JavaScript

Algo importante que conviene saber: JavaScript trae su propia versión de este patrón incorporada en el lenguaje, lo que se conoce como el protocolo de iteración. Los arrays, los String, los Set y los Map ya son iterables de forma nativa, y por eso podemos recorrerlos directamente con for...of:

const arr = [1, 2, 3, 4, 5];

// for...of consume el iterador nativo del array por nosotros
for (const value of arr) {
  console.log(value);
}

Aquí no escribimos ningún while ni manejamos índices a mano: esto sí se lee de forma declarativa, decimos qué queremos (recorrer cada valor) y no cómo avanzar paso a paso. La pregunta natural es: ¿cómo hace for...of para saber recorrer un array? La respuesta es que busca un método especial bajo la clave Symbol.iterator. Cualquier objeto que implemente [Symbol.iterator]() y devuelva un objeto con un método next() (que retorne { value, done }) se vuelve iterable.

Podemos reescribir nuestra clase para que respete ese contrato y así integrarse con todo el lenguaje:

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

  // Al implementar Symbol.iterator, nuestra clase se vuelve iterable de forma nativa
  [Symbol.iterator]() {
    let index = 0;
    const items = this.items;

    return {
      // El protocolo exige un método next() que retorne { value, done }
      next() {
        if (index < items.length) {
          return { value: items[index++], done: false };
        }
        return { value: undefined, done: true };
      },
    };
  }
}

const collection = new Collection([1, 2, 3, 4, 5]);

// Ahora podemos usar for...of directamente sobre nuestra instancia
for (const value of collection) {
  console.log(value);
}

La forma más cómoda de producir ese iterador es con un generador, una función que se declara con function* y va entregando valores con yield. El generador construye el objeto { value, done } por nosotros, así que el código queda mucho más corto:

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

  // Un generador implementa el protocolo de iteración por nosotros
  *[Symbol.iterator]() {
    for (const item of this.items) {
      // Cada yield entrega el siguiente valor de la colección
      yield item;
    }
  }
}

const collection = new Collection([1, 2, 3, 4, 5]);

for (const value of collection) {
  console.log(value);
}

La moraleja es clara: la clase Iterator que escribimos a mano al inicio es una excelente forma de entender el patrón por dentro, pero en JavaScript moderno casi siempre querrás apoyarte en Symbol.iterator, los generadores y for...of, porque así tus colecciones funcionan con todo lo que ya espera un iterable: la desestructuración, el operador de propagación ..., Array.from() y mucho más.

Nota: El protocolo de iteración es estable y forma parte del estándar de JavaScript desde ES6, así que puedes usarlo con confianza tanto en el navegador como en Node.js.

Déjame un comentario si lograste implementarlo, si quieres añadir alguna otra funcionalidad o si tienes alguna duda. Recuerda que si te gustó, también puedes compartir usando los links a las redes sociales aquí abajo.

Conclusiones

  • El patrón iterador es una forma ordenada de recorrer colecciones de objetos que nos da una interfaz uniforme y un mayor control sobre el proceso.
  • La implementación es simple y directa, y su utilidad es casi inmediata.
  • Añadir métodos utilitarios como hasNext() y reset() mejora aún más la flexibilidad de nuestra implementación.
  • JavaScript ya trae este patrón incorporado mediante Symbol.iterator, los generadores y for...of, que es la forma idiomática de aplicarlo hoy.

Ejercicios para practicar

  1. Recorre objetos tipo Set y Map en JavaScript usando for...of y compara la experiencia con nuestra clase hecha a mano.
  2. Extiende la clase Iterator (o el generador) para que permita aplicar funciones a los elementos de la colección mientras se recorren, por ejemplo filtrar o mapear elementos.
  3. Crea una aplicación que utilice el patrón iterador para recorrer y procesar datos de una API o una base de datos.

Resumen en 3 puntos

  1. El patrón iterador permite recorrer colecciones de objetos con una interfaz uniforme y mayor control.
  2. La implementación básica del patrón en JavaScript requiere los métodos first(), next() y current(), y conviene añadir utilitarios como hasNext() y reset().
  3. En JavaScript moderno este patrón ya está integrado en el lenguaje con Symbol.iterator, los generadores function* y for...of.

Eso es todo, espero que este post te sea de utilidad y lo puedas aplicar a algún proyecto que tengas en mente, y que te haya ayudado a entender la naturaleza del patrón iterador en JavaScript. Si tienes preguntas, comentarios o sugerencias, no dudes en dejar un comentario aquí abajo. ¡Buena suerte con tus proyectos y que sigas aprendiendo!

Sebastian Gomez

Sebastian Gomez

Creador de contenido principalmente acerca de tecnología.

Leave a Reply

0 Comments

Advertisements

Related Posts

Categorias