Ciclos e iteración en JavaScript
Para recordar
Sección titulada «Para recordar»Usa for...of para valores iterables y for...in para claves enumerables de un objeto. break termina, continue salta una iteración. Los métodos de Array expresan transformaciones; un ciclo manual ofrece más control. forEach no espera Promises.
Qué es un ciclo
Sección titulada «Qué es un ciclo»Un ciclo repite un bloque mientras exista trabajo o se cumpla una condición. La elección depende de si conoces la cantidad de iteraciones, recorres un iterable, necesitas propiedades de un objeto o esperas valores asíncronos.
| Estructura | Recorre o controla | Caso de uso |
|---|---|---|
for | contador y condición explícitos | índices, rangos y pasos |
while | condición evaluada antes | cantidad desconocida |
do...while | condición evaluada después | ejecutar al menos una vez |
for...of | valores de un iterable | Array, String, Map, Set, NodeList |
for...in | claves enumerables | objetos simples con comprobación propia |
for await...of | valores de iterable async o Promises | streams y paginación asíncrona |
Los ciclos son sentencias: no retornan una colección. Para transformar un array y obtener otro, map o filter suelen comunicar mejor el resultado.
Ciclo for
Sección titulada «Ciclo for»Tiene inicialización, condición y actualización. Cualquiera puede omitirse, pero una forma demasiado libre puede ser difícil de leer.
const values = []
for (let index = 0; index < 4; index += 1) { values.push(index * 2)}
values // [0, 2, 4, 6]Recorrer hacia atrás o con otro paso
Sección titulada «Recorrer hacia atrás o con otro paso»const reverseIndexes = []
for (let index = 5; index >= 1; index -= 2) { reverseIndexes.push(index)}
reverseIndexes // [5, 3, 1]Usa let para el contador. Cada iteración obtiene su binding de bloque, lo que evita el problema histórico de capturar una única variable var en callbacks.
const callbacks = []
for (let index = 0; index < 3; index += 1) { callbacks.push(() => index)}
callbacks.map((callback) => callback()) // [0, 1, 2]Evalúa la condición antes de cada vuelta, por lo que puede ejecutarse cero veces. Es apropiado cuando el final depende de un estado y no de un rango conocido.
let remaining = 13let attempts = 0
while (remaining > 1) { remaining = Math.ceil(remaining / 2) attempts += 1}
remaining // 1attempts // 4Mantén visible qué hace que la condición llegue a ser falsa. Un while (true) necesita una salida clara con break, retorno o excepción.
do...while
Sección titulada «do...while»Ejecuta el cuerpo antes de comprobar la condición, así que siempre corre al menos una vez.
let page = 1const visited = []
do { visited.push(page) page += 1} while (page <= 3)
visited // [1, 2, 3]Es útil para menús, reintentos o lectura inicial, siempre que la primera operación deba ocurrir incluso si la condición empieza siendo falsa.
for...of: valores iterables
Sección titulada «for...of: valores iterables»const totalByTag = new Map([ ["javascript", 12], ["css", 8]])
const rows = []
for (const [tag, total] of totalByTag) { rows.push(`${tag}: ${total}`)}
rows // ['javascript: 12', 'css: 8']Strings, arrays, typed arrays, Map, Set, NodeList, generators y muchos objetos del navegador son iterables. Un objeto simple no lo es automáticamente.
for (const character of "A😀B") { console.log(character)}// 'A'// '😀'// 'B'Necesitar índice y valor
Sección titulada «Necesitar índice y valor»const names = ["Ana", "Leo"]
for (const [index, name] of names.entries()) { console.log(index, name)}// 0 'Ana'// 1 'Leo'for...in: claves enumerables
Sección titulada «for...in: claves enumerables»for...in recorre nombres de propiedades enumerables, incluidas las heredadas. Para un objeto de datos, filtra con Object.hasOwn.
const settings = { theme: "dark", compact: true }const result = []
for (const key in settings) { if (!Object.hasOwn(settings, key)) continue result.push([key, settings[key]])}
result// [['theme', 'dark'], ['compact', true]]No uses for...in para valores de un array: entrega índices como strings y puede incluir propiedades adicionales.
const numbers = [10, 20]numbers.extra = 30
Object.keys(numbers) // ['0', '1', 'extra']
for (const key in numbers) console.log(key)// '0', '1', 'extra'
for (const value of numbers) console.log(value)// 10, 20Para objetos, Object.keys, Object.values y Object.entries suelen producir recorridos más explícitos.
break y continue
Sección titulada «break y continue»break termina el ciclo actual. continue omite el resto de la vuelta y continúa con la siguiente.
const input = [3, -1, 4, 0, 8]const positives = []
for (const number of input) { if (number === 0) break if (number < 0) continue positives.push(number)}
positives // [3, 4]Las etiquetas permiten salir de ciclos anidados, pero suelen indicar que conviene extraer una función y usar return.
function findPosition(matrix, expected) { for (let row = 0; row < matrix.length; row += 1) { for (let column = 0; column < matrix[row].length; column += 1) { if (matrix[row][column] === expected) return { row, column } } } return null}
findPosition( [ [1, 2], [3, 4] ], 3) // { row: 1, column: 0 }Métodos de Array o ciclo
Sección titulada «Métodos de Array o ciclo»| Necesidad | Opción expresiva |
|---|---|
| transformar todos | map |
| seleccionar algunos | filter |
| encontrar uno | find |
| comprobar alguno/todos | some / every |
| producir un acumulador | reduce o for...of |
| efectos síncronos simples | forEach |
| salida temprana o varias reglas | for...of |
await secuencial | for...of |
const prices = [10, 20, 30]
prices.map((price) => price * 2)// [20, 40, 60]
const doubled = []for (const price of prices) { doubled.push(price * 2)}// mismo resultado; más líneas, pero permite control adicionalCiclos asíncronos
Sección titulada «Ciclos asíncronos»Secuencia
Sección titulada «Secuencia»const saved = []
for (const item of items) { saved.push(await saveItem(item))}
saved // resultados en orden, una operación después de otraConcurrencia
Sección titulada «Concurrencia»const saved = await Promise.all(items.map((item) => saveItem(item)))
saved // resultados en orden de entrada; operaciones iniciadas juntasforEach(async () => ...) no espera las Promises del callback. Elige secuencia, concurrencia total o un límite de concurrencia según el servicio.
for await...of
Sección titulada «for await...of»async function* pages() { yield Promise.resolve(["a", "b"]) yield Promise.resolve(["c"])}
const items = []
for await (const page of pages()) { items.push(...page)}
items // ['a', 'b', 'c']Es especialmente útil con streams, async generators y fuentes paginadas que producen datos gradualmente.
Mutar mientras recorres
Sección titulada «Mutar mientras recorres»Eliminar o agregar elementos al mismo array durante la iteración puede saltar valores o extender el ciclo. Si necesitas filtrar, crea otra colección.
const original = [1, 2, 3, 4]const odd = original.filter((number) => number % 2 !== 0)
odd // [1, 3]original // [1, 2, 3, 4]Cuando la mutación local sea intencional, recorre de atrás hacia delante al eliminar por índice para no desplazar elementos pendientes.
Evitar ciclos infinitos y bloquear la interfaz
Sección titulada «Evitar ciclos infinitos y bloquear la interfaz»- Comprueba que el contador o estado avance hacia la salida.
- Define un máximo de intentos para datos externos.
- No recorras estructuras que pueden crecer sin límite dentro del mismo ciclo.
- Divide trabajo grande en lotes o muévelo a un worker.
- Añade cancelación a procesos asíncronos largos.
function findWithLimit(next, maximum = 1_000) { for (let attempt = 0; attempt < maximum; attempt += 1) { const value = next() if (value.done) return value.value }
throw new RangeError("Se alcanzó el límite de iteraciones")}