Dos bucles, dos trabajos distintos
JavaScript tiene dos formas de for que parecen casi calcadas, pero hacen cosas muy diferentes. for...of recorre los valores de un iterable. for...in recorre las claves de un objeto. Una letra de diferencia y un comportamiento completamente distinto.
Aquí tienes toda la historia en un solo ejemplo:
El primer bucle imprime apple, banana, cherry: los valores. El segundo imprime 0, 1, 2: las claves, y además como strings. Si te equivocas de bucle, te vas a pasar diez minutos preguntándote por qué tu array está lleno de números.
for...of en JavaScript: recorrer valores de cualquier iterable
for...of es el bucle al que vas a recurrir la mayoría de las veces. Funciona con cualquier cosa que JavaScript considere iterable: arrays, strings, Map, Set, NodeList, generadores y más.
Nada de andar haciendo malabares con índices ni de escribir scores[i]. Pides cada valor y recibes cada valor. Los strings también son iterables, así que for...of los recorre carácter a carácter:
Esto funciona correctamente con la mayoría de los puntos de código Unicode, lo que supone una pequeña pero real ventaja frente a acceder a los caracteres de una cadena por índice.
Obtener el índice con for...of
Lo único que for...of no te da directamente es la posición. Cuando la necesites, combina el bucle con entries():
names.entries() devuelve pares [índice, valor], y el destructuring los separa en dos variables. Suele quedar más limpio que recurrir al clásico for (let i = 0; ...) solo porque necesitas el i.
for...in: recorrer las claves de un objeto
for...in está pensado para objetos planos. Itera sobre las claves enumerables de tipo string:
Ojo: obtienes la clave, no el valor. Para leer el valor, tienes que volver a acceder al objeto con user[key]. Además, toda clave es un string, incluso cuando parece un número.
for...in también recorre la cadena de prototipos, así que puede acabar mostrando propiedades heredadas. En objetos literales propios rara vez te va a dar problemas, pero cuando iteras instancias de una clase u objetos que vienen de una librería, conviene curarse en salud:
Object.hasOwn(user, key) se salta todo lo que venga por herencia. En el código moderno, mucha gente prácticamente no usa for...in y prefiere Object.keys, Object.values o Object.entries, que es justo lo que vamos a ver a continuación.
La forma moderna de iterar un objeto en JavaScript
En lugar de for...in, lo habitual es combinar for...of con alguno de los helpers de Object.*. Eliges el que mejor se ajuste a lo que necesitas:
Object.entries se lleva la palma: desestructurar el par [key, value] se lee casi como una frase. Y como estos métodos sólo devuelven las propiedades propias y enumerables del objeto, te ahorras la basura heredada.
Evita for...in con arrays
Se puede usar, sí, pero es una fuente clásica de dolores de cabeza:
Obtienes 0, 1, 2 y tag. Cualquier propiedad que alguien haya añadido al array —o a Array.prototype mediante un polyfill— también aparece. Las claves son strings, así que key + 1 concatena en vez de sumar. Y el orden de iteración no está garantizado que coincida con el del array en todos los casos límite.
Regla práctica:
- ¿Valores del array?
for...of arr. - ¿Índice y valor del array?
for...of arr.entries(). - ¿Solo el índice, para contar? El clásico
for (let i = 0; i < arr.length; i++). - ¿Claves o entradas de un objeto?
Object.keys(obj)/Object.entries(obj)confor...of.
En resumen: for...in es una herramienta de nicho. Con for...of más los helpers de Object tienes cubierto todo lo que vas a necesitar.
break y continue funcionan en ambos
Los dos bucles admiten las típicas instrucciones de salida anticipada:
continue salta a la siguiente iteración; break sale del bucle por completo. Esta es la razón principal para preferir for...of frente a .forEach(): los callbacks de forEach no pueden romper el bucle con break, pero con for...of sí puedes hacerlo.
Comparativa lado a lado
Cuatro bucles, cuatro propósitos y un mismo modelo mental: si quieres obtener los valores de algo iterable, usa for...of. Si lo que necesitas es trabajar con las propiedades de un objeto, tira de Object.keys / Object.values / Object.entries, también con for...of. Deja for...in para ese caso excepcional en el que de verdad quieras recorrer todas las claves string enumerables de un objeto, heredadas incluidas.
Lo siguiente: valores truthy y falsy
Cada bucle y cada if en JavaScript termina haciéndose la misma pregunta: ¿este valor se considera verdadero? Y la respuesta no siempre es evidente: las cadenas vacías, el cero, null y undefined se comportan de forma distinta a lo que podrías imaginar. En la próxima sección entramos de lleno en los valores truthy y falsy.
Preguntas frecuentes
¿Cuál es la diferencia entre for...of y for...in en JavaScript?
for...of recorre los valores de un iterable como un array, un string, un Map o un Set. for...in, en cambio, recorre las claves (nombres de propiedad) de un objeto. Para un array como ['a', 'b'], for...of te devuelve 'a' y 'b'; for...in te da '0' y '1' como strings.
¿Puedo usar for...of con un objeto?
Directamente no: los objetos planos no son iterables. Lo que sí puedes hacer es convertirlo en algo iterable con Object.keys(obj), Object.values(obj) o Object.entries(obj) y luego recorrerlo con for...of. El patrón habitual es for (const [key, value] of Object.entries(obj)).
¿Por qué no conviene usar for...in con arrays?
Técnicamente funciona, pero for...in recorre todas las propiedades enumerables, incluidas las heredadas y cualquier cosa que alguien haya añadido a Array.prototype. Además, devuelve las claves como strings y el orden no está garantizado al 100% para claves numéricas en todos los casos. Mejor usa for...of si quieres los valores, o un for clásico cuando necesites el índice.