Os laços for...of e for...in são duas formas de iterar sobre coleções em JavaScript. Embora pareçam semelhantes, eles servem a propósitos distintos e têm comportamentos diferentes que podem gerar confusão. Nesta aula, vamos explorar cada um deles, comparar seu uso e destacar as armadilhas mais comuns.

Entender a diferença entre eles é essencial para escrever código limpo e previsível. O for...of itera sobre valores de objetos iteráveis (como arrays, strings, Map, Set), enquanto o for...in itera sobre as chaves (propriedades) enumeráveis de um objeto, incluindo as herdadas da cadeia de protótipos.

Iterando arrays e objetos

O for...of é ideal para percorrer arrays e outros iteráveis. Ele fornece diretamente cada valor da coleção, sem se preocupar com índices. Veja um exemplo com array:

const frutas = ['maçã', 'banana', 'laranja'];
for (const fruta of frutas) {
  console.log(fruta);
}
// maçã
// banana
// laranja

Já o for...in itera sobre as chaves de um objeto. Quando usado em um array, ele retorna os índices como strings, o que pode ser surpreendente:

const frutas = ['maçã', 'banana', 'laranja'];
for (const indice in frutas) {
  console.log(indice); // '0', '1', '2'
}

Para objetos literais, for...in percorre as propriedades enumeráveis, incluindo as herdadas. Exemplo:

const pessoa = { nome: 'João', idade: 30 };
for (const chave in pessoa) {
  console.log(chave + ': ' + pessoa[chave]);
}
// nome: João
// idade: 30

Note que for...of não funciona diretamente com objetos comuns, pois eles não são iteráveis por padrão. Para iterar sobre valores de um objeto, podemos usar Object.values() ou Object.entries():

for (const valor of Object.values(pessoa)) {
  console.log(valor);
}
// João
// 30

Diferença crucial entre eles

A principal diferença é que for...of acessa os valores de um iterável, enquanto for...in acessa as chaves (nomes de propriedades) de um objeto. Isso tem implicações importantes no comportamento:

  • for...of só funciona com objetos que implementam o protocolo de iteração (Symbol.iterator). Arrays, strings, Map, Set e NodeList são exemplos.
  • for...in funciona com qualquer objeto, mas não é recomendado para arrays porque ele itera sobre índices como strings e também pode incluir propriedades adicionadas ao protótipo do Array.
  • for...in percorre propriedades enumeráveis herdadas, o que pode causar resultados inesperados se não for usado com hasOwnProperty.

Exemplo comparativo com um array que possui uma propriedade extra:

const arr = [10, 20, 30];
arr.propExtra = 'olá';

for (const valor of arr) {
  console.log(valor); // 10, 20, 30 (ignora propExtra)
}

for (const chave in arr) {
  console.log(chave); // '0', '1', '2', 'propExtra'
}

Isso mostra que for...in inclui a propriedade extra, enquanto for...of não. Portanto, para arrays, prefira for...of (ou métodos como forEach).

Pegadinhas

Algumas armadilhas comuns ao usar esses laços:

  • Usar for...in em arrays: Além de incluir propriedades adicionadas, a ordem de iteração não é garantida (embora na prática seja numérica crescente para índices). Isso pode quebrar código que depende da ordem.
  • Iterar sobre strings: for...of itera sobre caracteres, enquanto for...in itera sobre índices (0, 1, ...). Exemplo:
const texto = 'abc';
for (const char of texto) console.log(char); // 'a', 'b', 'c'
for (const idx in texto) console.log(idx); // '0', '1', '2'
  • Propriedades herdadas: Ao usar for...in em um objeto, propriedades do protótipo podem aparecer. Por exemplo, se você estender Object.prototype (não recomendado), elas serão listadas. Para evitar, use hasOwnProperty:
const obj = { a: 1 };
for (const chave in obj) {
  if (obj.hasOwnProperty(chave)) {
    console.log(chave); // apenas 'a'
  }
}
  • Não usar for...of em objetos: Tentar usar for...of diretamente em um objeto comum lança um erro TypeError: obj is not iterable. É necessário usar Object.keys(), Object.values() ou Object.entries().
  • Confundir chave e valor: Em for...in, a variável recebe a chave (nome da propriedade), não o valor. É comum iniciantes tentarem acessar pessoa.chave em vez de pessoa[chave].

Em resumo: use for...of para arrays, strings, Map, Set e outros iteráveis. Use for...in apenas para objetos, e com cuidado, filtrando propriedades herdadas quando necessário.

Boas práticas

  • Prefira for...of para iterar sobre arrays, a menos que você precise explicitamente dos índices.
  • Ao usar for...in, sempre considere o uso de hasOwnProperty para evitar propriedades do protótipo.
  • Evite adicionar propriedades a objetos nativos como Array.prototype, pois isso afeta laços for...in.
  • Para objetos, considere usar Object.entries() com for...of para obter pares chave-valor de forma mais segura.

Referências

Exercícios

  1. Exercício 1: Dado o array const numeros = [1, 2, 3, 4, 5];, use for...of para imprimir o dobro de cada número no console.

    ✓ Resposta:
    const numeros = [1, 2, 3, 4, 5];
    for (const num of numeros) {
      console.log(num * 2);
    }
  2. Exercício 2: Dado o objeto const carro = { marca: 'Toyota', modelo: 'Corolla', ano: 2020 };, use for...in para imprimir cada chave e seu valor no console.

    ✓ Resposta:
    const carro = { marca: 'Toyota', modelo: 'Corolla', ano: 2020 };
    for (const chave in carro) {
      if (carro.hasOwnProperty(chave)) {
        console.log(chave + ': ' + carro[chave]);
      }
    }
  3. Exercício 3: Explique por que usar for...in em um array pode ser problemático. Dê um exemplo.

    ✓ Resposta:Usar for...in em arrays pode incluir propriedades adicionadas ao array ou herdadas do protótipo, além de iterar sobre índices como strings. Exemplo:
    const arr = [1, 2];
    arr.prop = 'extra';
    for (const key in arr) {
      console.log(key); // '0', '1', 'prop'
    }
  4. Exercício 4: Dada a string const palavra = 'JavaScript';, use for...of para imprimir cada caractere em uma linha separada.

    ✓ Resposta:
    const palavra = 'JavaScript';
    for (const char of palavra) {
      console.log(char);
    }
  5. Exercício 5: Escreva um código que use for...of com Object.entries() para iterar sobre as propriedades de um objeto e imprimir chave e valor.

    ✓ Resposta:
    const obj = { a: 1, b: 2 };
    for (const [chave, valor] of Object.entries(obj)) {
      console.log(chave + ': ' + valor);
    }