Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA
LiçãoIniciantecódigo testado

Percorrer array em JavaScript: forEach, for of e for

for...of itera valores, for...in itera chaves e pega as herdadas, e forEach não aceita break nem continue. Qual laço usar em cada caso, com saída real.

Rodolfo Mori7 min de leitura

Para percorrer um array em JavaScript existem três caminhos usados no dia a dia: for clássico quando você precisa do índice e do controle total, for...of quando você só quer os valores e pode precisar parar no meio, e forEach quando você vai rodar um efeito para cada item e nunca vai interromper.

O quarto, for...in, existe e não é para array: ele percorre chaves, não valores, e traz junto tudo que estiver pendurado no prototype.

A escolha não é estilo. Cada laço abre mão de uma coisa: forEach abre mão de break, continue e do return que você acha que está escrevendo; for...in abre mão da garantia de que só os itens vão aparecer. Esta lição mostra cada perda com o código rodando.

Três formas de percorrer a mesma prateleira

Imagine conferir uma prateleira. Você pode andar com uma planilha de posições, olhar cada produto diretamente ou pedir a outra pessoa que execute a mesma tarefa em todos eles. Os três caminhos chegam aos itens, mas só alguns deixam voltar, pular ou interromper a conferência no meio.

No JavaScript, esses caminhos são for, for...of e forEach. O primeiro expõe o índice e todo o controle; o segundo entrega valores e aceita break; o terceiro chama um callback para cada item e não pode ser interrompido dessa forma. No primeiro exemplo, anote índice e valor de cada volta antes de conferir a saída — essa tabela pequena revela qual ferramenta entrega o controle que a tarefa pede.

js
const carrinho = ['Teclado mecânico', 'Mouse sem fio', 'Headset'];

for (let i = 0; i < carrinho.length; i++) {
  console.log(`${i + 1}. ${carrinho[i]}`);
}
1. Teclado mecânico 2. Mouse sem fio 3. Headset

Verboso, e ainda assim insubstituível em três situações: quando você precisa andar de dois em dois, quando precisa percorrer de trás para a frente, e quando vai remover itens durante o percurso — os três casos em que o índice é o assunto, não um detalhe.

Note o let i, não var i. Com var, todas as voltas compartilham a mesma variável, e qualquer função criada dentro do laço enxerga o valor final. É o clássico que a lição de variáveis em JavaScript reproduz no terminal.

for...of: o laço padrão de hoje

js
const pedidos = [
  { numero: 1041, total: 289.9 },
  { numero: 1042, total: 1199 },
  { numero: 1043, total: 79.5 },
];

for (const pedido of pedidos) {
  if (pedido.total < 100) continue;
  if (pedido.total > 1000) {
    console.log(`Pedido ${pedido.numero} vai para análise manual`);
    break;
  }
  console.log(`Pedido ${pedido.numero} liberado: R$ ${pedido.total.toFixed(2)}`);
}
Pedido 1041 liberado: R$ 289.90 Pedido 1042 vai para análise manual

Repare no que não foi impresso: o pedido 1043 nunca chegou a ser avaliado, porque o break encerrou o laço. Esse é o poder que for...of tem e forEach não tem.

for...of funciona em qualquer coisa iterável, não só em array: String, Set, Map, NodeList, arguments, o retorno de .entries(). É a razão de ele ter virado o laço padrão.

js
const skus = new Set(['TEC-001', 'MOU-014', 'TEC-001']);
const precos = new Map([
  ['TEC-001', 289.9],
  ['MOU-014', 149.9],
]);

for (const sku of skus) {
  console.log(sku, precos.get(sku));
}

for (const [sku, preco] of precos) {
  console.log(`${sku}: R$ ${preco.toFixed(2)}`);
}
TEC-001 289.9 MOU-014 149.9 TEC-001: R$ 289.90 MOU-014: R$ 149.90

O Set derrubou o SKU repetido sozinho, e o Map entregou par a par, pronto para desestruturar. O mesmo for...of serviu para os dois.

forEach: um efeito por item, e nada de sair no meio

forEach recebe uma função e a chama para cada item. Ela ganha três argumentos: o item, o índice e o array inteiro.

js
const carrinho = [
  { nome: 'Teclado mecânico', preco: 289.9 },
  { nome: 'Mouse sem fio', preco: 149.9 },
  { nome: 'Headset', preco: 399 },
];

carrinho.forEach((item, indice, lista) => {
  const ultimo = indice === lista.length - 1 ? ' (último)' : '';
  console.log(`${indice}: ${item.nome} — R$ ${item.preco.toFixed(2)}${ultimo}`);
});
0: Teclado mecânico — R$ 289.90 1: Mouse sem fio — R$ 149.90 2: Headset — R$ 399.00 (último)

Lê bem, e para renderizar uma lista é o que a maioria escreve. O preço vem agora.

O return dentro do forEach não é o que você pensa

Como o corpo do laço é uma função, o return pertence a ela, não a quem chamou o forEach. E o forEach descarta esse valor:

js
const precos = [289.9, 149.9, 399, 79.5];

function primeiroCaro(lista) {
  lista.forEach((preco) => {
    if (preco > 200) {
      return preco;
    }
  });
  return 'nenhum';
}

console.log(primeiroCaro(precos));

let visitados = 0;
precos.forEach((preco) => {
  visitados += 1;
  if (preco > 200) return;
  console.log('barato:', preco);
});
console.log('itens visitados:', visitados);
nenhum barato: 149.9 barato: 79.5 itens visitados: 4

Duas lições numa saída só. A primeira: primeiroCaro devolveu 'nenhum' mesmo tendo três itens acima de 200 — o return preco saiu do callback e ninguém pegou o valor. A segunda: o return do segundo bloco funcionou como continue, pulando a impressão, mas os quatro itens foram visitados. Ele não é break.

E break nem chega a rodar: break é sintaxe de laço, e ali dentro não existe laço nenhum, só uma chamada de função.

for...in percorre chaves — inclusive as herdadas

js
const carrinho = ['Teclado mecânico', 'Mouse sem fio', 'Headset'];

Array.prototype.freteGratis = true;

for (const chave in carrinho) {
  console.log(typeof chave, JSON.stringify(chave), carrinho[chave]);
}
string "0" Teclado mecânico string "1" Mouse sem fio string "2" Headset string "freteGratis" true

Duas coisas quebradas de uma vez.

A primeira está na coluna do typeof: as chaves saem como string. Faça chave + 1 esperando o próximo índice e você recebe "01", não 1.

A segunda é a quarta linha. Alguém — uma biblioteca antiga, um polyfill mal escrito, você mesmo em outro arquivo — acrescentou uma propriedade ao Array.prototype, e ela entrou no laço como se fosse item do carrinho. O for...in não pergunta se a chave é do objeto ou de um ancestral dele.

Um array esparso torna isso pior ainda: for...in pula os buracos, então o número de voltas nem bate com o length.

for...in é a ferramenta certa para percorrer as chaves de um objeto. Em array, use for...of.

Índice e valor juntos: entries()

Se você escolheu for...of mas precisa do índice, não volte para o for clássico:

js
const carrinho = ['Teclado mecânico', 'Mouse sem fio', 'Headset'];

for (const [indice, nome] of carrinho.entries()) {
  console.log(`${indice + 1}º item do pedido: ${nome}`);
}

console.log([...carrinho.keys()]);
1º item do pedido: Teclado mecânico 2º item do pedido: Mouse sem fio 3º item do pedido: Headset [ 0, 1, 2 ]

entries() devolve pares [índice, valor], e a desestruturação separa os dois na própria linha do for. A última saída é a prova do que importa aqui: keys() mostra os índices espalhados como 0, 1, 2números, sem aspas. Diferente do for...in, indice + 1 soma em vez de concatenar.

Remover enquanto percorre pula item

js
const carrinho = ['Teclado mecânico', 'Mouse sem fio', 'Headset', 'Webcam'];

for (const [indice, nome] of carrinho.entries()) {
  if (nome === 'Headset') {
    carrinho.splice(indice, 1);
  }
  console.log(indice, nome);
}

console.log(carrinho);
0 Teclado mecânico 1 Mouse sem fio 2 Headset [ 'Teclado mecânico', 'Mouse sem fio', 'Webcam' ]

O array tinha quatro itens e o laço rodou três vezes. 'Webcam' nunca foi impressa: quando o Headset saiu da posição 2, a Webcam deslizou para lá, o laço avançou para a posição 3 e não achou mais nada.

Duas saídas, ambas melhores que “tomar cuidado”: percorra de trás para a frente com o for clássico, ou construa um array novo com filter. Se você vai remover mesmo, a lição adicionar e remover elementos de um array tem o inventário completo dos métodos que mutam.

Parar cedo sem escrever laço: some, find, every

Muita busca escrita com laço na mão já tem um método que faz o mesmo e para sozinho no primeiro acerto:

js
const carrinho = [
  { nome: 'Teclado mecânico', estoque: 4 },
  { nome: 'Mouse sem fio', estoque: 0 },
  { nome: 'Headset', estoque: 9 },
];

let consultas = 0;
const semEstoque = carrinho.some((item) => {
  consultas += 1;
  return item.estoque === 0;
});

console.log(semEstoque, 'depois de', consultas, 'consultas');
console.log(carrinho.every((item) => item.estoque >= 0));
console.log(carrinho.find((item) => item.estoque === 0)?.nome);
console.log(carrinho.findIndex((item) => item.estoque === 0));
true depois de 2 consultas true Mouse sem fio 1

O contador é a prova: some parou na segunda consulta, sem tocar no Headset. every faz o simétrico — para no primeiro item que reprova. find devolve o item, findIndex devolve a posição, e ambos param também.

O erro: forEach is not a function

Este erro aparece na primeira vez que alguém tenta percorrer elementos da página. Reproduzido aqui com um objeto que tem exatamente o formato de uma HTMLCollection — chaves numéricas e length, sem herdar de Array:

js
const elementos = { 0: 'Teclado mecânico', 1: 'Mouse sem fio', length: 2 };

elementos.forEach((nome) => console.log(nome));
file:///private/tmp/loja/elementos.mjs:3 elementos.forEach((nome) => console.log(nome)); ^ TypeError: elementos.forEach is not a function at file:///private/tmp/loja/elementos.mjs:3:11 at ModuleJob.run (node:internal/modules/esm/module_job:439:25) at async node:internal/modules/esm/loader:633:26 at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5) Node.js v24.16.0

No navegador a mensagem é idêntica, sem o caminho do arquivo. E a causa é sempre a mesma: document.getElementsByClassName('item') e document.getElementsByTagName('li') devolvem uma HTMLCollection, que se parece com array — tem length, tem colecao[0] — mas não herda de Array.prototype. Nada de forEach, map ou filter.

O detalhe que confunde: document.querySelectorAll('.item') devolve uma NodeList, e essa tem forEach. Não tem map nem filter. Por isso o mesmo código funciona com um seletor e falha com o outro.

A correção é converter para array de verdade antes:

js
const elementos = { 0: 'Teclado mecânico', 1: 'Mouse sem fio', length: 2 };

const lista = Array.from(elementos);
console.log(Array.isArray(elementos), Array.isArray(lista));

lista.forEach((nome, i) => console.log(i, nome));

for (const nome of lista) {
  console.log('for...of:', nome);
}
false true 0 Teclado mecânico 1 Mouse sem fio for...of: Teclado mecânico for...of: Mouse sem fio

Array.from(colecao) funciona com qualquer coisa que tenha length ou seja iterável. O spread, [...colecao], é mais curto e só funciona com iteráveis — o que cobre NodeList e HTMLCollection, mas não um objeto array-like puro como o deste exemplo.

Qual laço usar

situação laço por quê
só os valores, sem parar for...of mais legível, funciona em iterável
precisa de break for...of forEach não tem
precisa do índice for...of + entries() índice é número, não string
efeito por item, lista curta forEach expressa a intenção
remover durante o percurso for clássico, de trás para a frente evita o item pulado
existe algum que…? some / find param no primeiro acerto
chaves de um objeto for...in é o caso de uso real dele
transformar a lista map / filter laço nenhum

Repare na última linha: uma boa parte dos laços escritos por iniciante não deveria ser laço. Percorrer para montar outro array é map; percorrer para selecionar é filter; percorrer somando é reduce. É o assunto da próxima lição da trilha de JavaScript, map, filter e reduce.

Antes dela, se carrinho[i] estiver devolvendo undefined no meio do laço, volte para arrays em JavaScript — o length e os buracos explicam a maioria dos casos. O guia completo de JavaScript mostra a trilha inteira.

Prefere aprender em vídeo?

Tem aula sobre este assunto no nosso canal.

Ver todos os vídeos do canal
  • arrays
  • laco
  • foreach
  • for of
  • iteracao

Perguntas frequentes

Posso usar break dentro de forEach?
Não. break é sintaxe de laço e forEach é uma função — o parser rejeita antes de rodar. Se você precisa parar no meio, o laço certo é for...of, ou um método que já para sozinho, como some, find ou findIndex.
Por que for...in é desaconselhado em array?
Porque ele percorre as chaves enumeráveis do objeto, inclusive as herdadas do prototype e as propriedades não numéricas que alguém pendurou no array. E as chaves vêm como string, então indice + 1 vira concatenação.
forEach é mais lento que o for clássico?
Numa lista de tela, a diferença é irrelevante e o motor costuma inlinar a chamada. Ela só aparece em laços muito quentes, com centenas de milhares de itens. Escolha por legibilidade e pela necessidade de parar no meio.
Posso remover itens do array enquanto percorro?
Pode, e é onde nasce o item pulado: ao remover a posição atual, o índice seguinte desliza para o lugar dela e o laço passa por cima. Percorra de trás para a frente ou monte um array novo com filter.

Dúvidas e comentários

Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.

Todo o código deste artigo foi executado em Node 24.16.0, e as saídas exibidas são as reais — como produzimos este conteúdo.

Fontes consultadas

  1. MDN — Loops and iteration — developer.mozilla.org
  2. MDN — Array.prototype.forEach() — developer.mozilla.org
  3. MDN — HTMLCollection — developer.mozilla.org

Continue por aqui