Dois laços parecidos, funções bem diferentes
O JavaScript tem dois tipos de laço que parecem quase iguais, mas fazem coisas bem distintas. O for...of percorre os valores de um iterável. Já o for...in percorre as chaves de um objeto. Uma letrinha de diferença, um comportamento completamente diferente.
Dá pra entender a história toda com um único exemplo:
O primeiro loop imprime apple, banana, cherry — os valores. Já o segundo imprime 0, 1, 2 — as chaves, em forma de string. Se você escolher o errado, vai passar dez minutos se perguntando por que seu array está cheio de números.
for...of: valores de qualquer coisa iterável
O for...of é o laço que você vai usar na maior parte do tempo. Ele funciona em qualquer coisa que o JavaScript considere iterável: arrays, strings, Map, Set, NodeList, generators e por aí vai.
Sem ficar gerenciando índice na mão, sem scores[i]. Você pede cada valor e recebe cada valor. Strings também são iteráveis — o for...of percorre caractere por caractere:
Isso funciona direitinho com a maioria dos code points Unicode também, o que já é uma vantagem pequena, mas real, em relação a acessar a string por índice numérico.
Como pegar o índice no for...of
A única coisa que o for...of não te entrega de graça é a posição atual. Quando você precisa dela, é só combinar o laço com entries():
names.entries() devolve pares no formato [índice, valor], e a desestruturação no começo do laço já separa isso em duas variáveis. Costuma ficar bem mais limpo do que recorrer ao clássico for (let i = 0; ...) só porque você precisa do i.
for...in: percorrendo as chaves de um objeto
O for...in foi feito para objetos comuns. Ele percorre as chaves enumeráveis (strings) do objeto:
Repare que você recebe a chave, não o valor. Para acessar o valor, basta indexar o objeto de volta com user[key]. E tem um detalhe: toda chave vem como string — mesmo quando parece um número.
O for...in também percorre a cadeia de protótipos, ou seja, pode acabar expondo propriedades herdadas. Em objetos literais do seu próprio código isso raramente incomoda, mas quando você itera sobre instâncias de uma classe ou objetos vindos de alguma biblioteca, vale a pena se proteger:
Object.hasOwn(user, key) ignora qualquer coisa herdada. Em código moderno, a maioria das pessoas evita usar for...in e prefere Object.keys, Object.values ou Object.entries — o que nos leva direto para a próxima seção.
A forma moderna de percorrer um objeto em JavaScript
Em vez de for...in, combine for...of com um dos helpers Object.*. É só escolher qual dos três encaixa melhor no que você precisa:
Object.entries fica especialmente gostoso de ler — desestruturar o par [chave, valor] soa quase como texto em português. E, como esses métodos devolvem só as propriedades próprias enumeráveis do objeto, você não precisa se preocupar com lixo herdado do prototype.
Evite for...in em arrays
Até funciona, tecnicamente, mas é cilada na certa:
Você recebe 0, 1, 2 e tag. Qualquer propriedade que alguém tenha adicionado ao array — ou ao Array.prototype via polyfill — também aparece. As chaves são strings, então key + 1 faz concatenação no lugar de soma. E a ordem de iteração não é garantida em todos os casos extremos.
Regrinha prática:
- Só os valores do array?
for...of arr. - Índice e valor do array?
for...of arr.entries(). - Só o índice, contando? O clássico
for (let i = 0; i < arr.length; i++). - Chaves ou entradas de um objeto?
Object.keys(obj)/Object.entries(obj)combinados comfor...of.
Resumindo: o for...in é ferramenta de nicho. O for...of junto com os helpers de Object resolve tudo que você precisa no dia a dia.
break e continue funcionam nos dois
Os dois laços aceitam os recursos habituais para sair mais cedo:
continue pula para a próxima iteração; break sai do laço de uma vez. Esse é o principal motivo para preferir for...of em vez de .forEach() — dentro do callback do forEach não dá para usar break, mas no for...of dá numa boa.
Resumo lado a lado
Quatro laços, quatro propósitos, e um único modelo mental: se você quer os valores de algo iterável, use for...of. Se precisa mexer com as propriedades de um objeto, passe por Object.keys / Object.values / Object.entries — também com for...of. Já o for...in fica reservado para aquele caso raro em que você realmente precisa de todas as chaves string enumeráveis de um objeto, herdadas ou não.
A seguir: valores truthy e falsy
Todo if e todo laço em JavaScript acaba fazendo a mesma pergunta por baixo dos panos: esse valor é considerado verdadeiro? E a resposta nem sempre é óbvia — strings vazias, o zero, null e undefined se comportam de formas bem diferentes do que você imagina. No próximo tópico, vamos entender os valores truthy e falsy.
Perguntas frequentes
Qual a diferença entre for...of e for...in no JavaScript?
O for...of percorre os valores de algo iterável, como array, string, Map ou Set. Já o for...in percorre as chaves (nomes das propriedades) de um objeto. Num array ['a', 'b'], o for...of devolve 'a' e 'b'; o for...in devolve '0' e '1' — como strings.
Dá pra usar for...of direto num objeto?
Direto não — objetos comuns não são iteráveis. O jeito é usar Object.keys(obj), Object.values(obj) ou Object.entries(obj) pra gerar um array e aí sim iterar com for...of. O padrão mais comum é for (const [key, value] of Object.entries(obj)).
Por que evitar for...in em arrays?
Até funciona, mas o for...in passa por toda propriedade enumerável, inclusive as herdadas e qualquer coisa que alguém tenha adicionado em Array.prototype. Além disso, as chaves vêm como string e a ordem nem sempre é garantida. Prefira for...of pros valores, ou o for clássico quando precisar do índice.