Arrays: criando e manipulando listas

[35] Arrays: criando e manipulando listas

O sort() sem argumento ordena como se tudo fosse texto, e aí 10 vem antes de 9. Fora essa armadilha, o artigo apresenta os três métodos que mudam a maneira de lidar com listas — map, filter e reduce —, o encadeamento que se lê como frase, e a fronteira entre o que muta o original e o que devolve um novo.
Javascript

11 min de leitura

Até agora trabalhamos com variáveis que guardam um único valor em javascript. Mas e quando precisamos guardar uma lista de produtos, uma coleção de usuários, ou uma série de notas de um aluno? Para isso existem os arrays.

Arrays são listas ordenadas que podem guardar qualquer tipo de dado. São uma das estruturas mais usadas no JavaScript — e dominá-los, especialmente seus métodos modernos, é o que separa um iniciante de um desenvolvedor produtivo.

Criando arrays

// Array de strings
const frutas = ["maçã", "banana", "laranja"];

// Array de números
const notas = [8.5, 7.0, 9.5, 6.0];

// Array misto (possível, mas evite na prática)
const misturado = ["João", 25, true, null];

// Array vazio
const lista = [];

Arrays usam colchetes [] e os itens são separados por vírgula.

Acessando elementos

Cada elemento tem um índice que começa em zero:

const frutas = ["maçã", "banana", "laranja", "uva"];

console.log(frutas[0]); // maçã
console.log(frutas[1]); // banana
console.log(frutas[3]); // uva
console.log(frutas[4]); // undefined — não existe

// Último elemento — independente do tamanho:
console.log(frutas[frutas.length - 1]); // uva

Modificando elementos

const frutas = ["maçã", "banana", "laranja"];

frutas[1] = "manga"; // substituindo banana por manga
console.log(frutas); // ["maçã", "manga", "laranja"]

Propriedade length

const notas = [8.5, 7.0, 9.5, 6.0];
console.log(notas.length); // 4

Métodos essenciais — adicionando e removendo

const times = ["Flamengo", "Palmeiras"];

// Adiciona no final
times.push("Grêmio");
console.log(times); // ["Flamengo", "Palmeiras", "Grêmio"]

// Remove do final e retorna o elemento removido
const removido = times.pop();
console.log(removido); // "Grêmio"
console.log(times);    // ["Flamengo", "Palmeiras"]

// Adiciona no início
times.unshift("Santos");
console.log(times); // ["Santos", "Flamengo", "Palmeiras"]

// Remove do início e retorna o elemento removido
const primeiro = times.shift();
console.log(primeiro); // "Santos"
console.log(times);    // ["Flamengo", "Palmeiras"]

Encontrando elementos

const numeros = [10, 20, 30, 40, 50];

// indexOf — retorna o índice ou -1 se não encontrar
console.log(numeros.indexOf(30));  // 2
console.log(numeros.indexOf(99));  // -1

// includes — retorna true ou false
console.log(numeros.includes(40)); // true
console.log(numeros.includes(99)); // false

// find — retorna o primeiro elemento que satisfaz a condição
const encontrado = numeros.find(n => n > 25);
console.log(encontrado); // 30

// findIndex — retorna o índice do primeiro elemento que satisfaz
const indice = numeros.findIndex(n => n > 25);
console.log(indice); // 2

Os três métodos mais importantes — map, filter e reduce

Estes três métodos transformam a forma como você trabalha com listas. São funcionais, elegantes e amplamente usados em React, Node e em qualquer código moderno.

map — transformar cada elemento

map percorre o array e retorna um novo array com cada elemento transformado pela função que você passa:

const precos = [100, 250, 80, 400];

// Aplicar 10% de desconto em todos os preços
const comDesconto = precos.map(preco => preco * 0.9);
console.log(comDesconto); // [90, 225, 72, 360]

// O array original não é alterado
console.log(precos); // [100, 250, 80, 400]

Outro exemplo — transformar objetos:

const usuarios = [
  { nome: "Ana", idade: 25 },
  { nome: "Carlos", idade: 30 },
  { nome: "Bia", idade: 22 },
];

const nomes = usuarios.map(usuario => usuario.nome);
console.log(nomes); // ["Ana", "Carlos", "Bia"]

filter — filtrar elementos

filter percorre o array e retorna um novo array apenas com os elementos que passam na condição:

const notas = [4.5, 7.0, 8.5, 3.0, 9.5, 5.5];

const aprovados = notas.filter(nota => nota >= 6);
console.log(aprovados); // [7.0, 8.5, 9.5]

const reprovados = notas.filter(nota => nota < 6);
console.log(reprovados); // [4.5, 3.0, 5.5]

Com objetos:

const produtos = [
  { nome: "Notebook", preco: 3500, disponivel: true },
  { nome: "Mouse", preco: 80, disponivel: false },
  { nome: "Teclado", preco: 200, disponivel: true },
  { nome: "Monitor", preco: 1200, disponivel: false },
];

const disponiveis = produtos.filter(p => p.disponivel);
console.log(disponiveis);
// [{ nome: "Notebook", ... }, { nome: "Teclado", ... }]

const baratos = produtos.filter(p => p.preco < 500);
console.log(baratos);
// [{ nome: "Mouse", ... }, { nome: "Teclado", ... }]

reduce — reduzir a um único valor

reduce é o mais poderoso e o mais temido pelos iniciantes. Ele percorre o array acumulando um resultado — pode ser uma soma, um objeto, outro array, qualquer coisa.

// Sintaxe: array.reduce((acumulador, elementoAtual) => ..., valorInicial)

const numeros = [1, 2, 3, 4, 5];

const soma = numeros.reduce((acc, num) => acc + num, 0);
console.log(soma); // 15

Passo a passo do que acontece:

  • Começa com acc = 0
  • Rodada 1: acc = 0 + 1 = 1
  • Rodada 2: acc = 1 + 2 = 3
  • Rodada 3: acc = 3 + 3 = 6
  • Rodada 4: acc = 6 + 4 = 10
  • Rodada 5: acc = 10 + 5 = 15

Exemplo prático — total de um carrinho de compras:

const carrinho = [
  { produto: "Notebook", preco: 3500 },
  { produto: "Mouse", preco: 80 },
  { produto: "Teclado", preco: 200 },
];

const total = carrinho.reduce((acc, item) => acc + item.preco, 0);
console.log(`Total: R$ ${total}`); // Total: R$ 3780

Encadeando métodos

O verdadeiro poder aparece quando você combina map, filter e reduce em sequência:

const funcionarios = [
  { nome: "Ana", salario: 3000, ativo: true },
  { nome: "Carlos", salario: 5000, ativo: false },
  { nome: "Bia", salario: 4000, ativo: true },
  { nome: "Diego", salario: 2500, ativo: true },
];

// Total da folha apenas dos funcionários ativos com salário acima de 2800
const totalFolha = funcionarios
  .filter(f => f.ativo)
  .filter(f => f.salario > 2800)
  .reduce((acc, f) => acc + f.salario, 0);

console.log(`Total da folha: R$ ${totalFolha}`); // R$ 7000

Leia de cima para baixo como uma frase: pegue os funcionários, filtre os ativos, filtre os com salário acima de 2800, some os salários.

Outros métodos úteis

const letras = ["c", "a", "b", "e", "d"];

// sort — ordena (modifica o array original!)
letras.sort();
console.log(letras); // ["a", "b", "c", "d", "e"]

// reverse — inverte
letras.reverse();
console.log(letras); // ["e", "d", "c", "b", "a"]

// slice — retorna uma parte do array (não modifica o original)
const frutas = ["maçã", "banana", "laranja", "uva", "kiwi"];
const algumas = frutas.slice(1, 3);
console.log(algumas); // ["banana", "laranja"]

// join — transforma o array em string
const palavras = ["JavaScript", "é", "incrível"];
console.log(palavras.join(" ")); // "JavaScript é incrível"

// flat — achata arrays aninhados
const aninhado = [1, [2, 3], [4, [5, 6]]];
console.log(aninhado.flat());    // [1, 2, 3, 4, [5, 6]]
console.log(aninhado.flat(2));   // [1, 2, 3, 4, 5, 6]

Cuidado com a mutação

Alguns métodos modificam o array original (push, pop, shift, unshift, sort, reverse, splice). Outros retornam um novo array sem tocar no original (map, filter, slice, concat).

Em código moderno e funcional, preferimos não mutar os dados originais:

const numeros = [3, 1, 4, 1, 5];

// ❌ Modifica o array original
numeros.sort();

// ✅ Cria uma cópia ordenada sem alterar o original
const ordenados = [...numeros].sort();
console.log(numeros);   // [3, 1, 4, 1, 5] — intacto
console.log(ordenados); // [1, 1, 3, 4, 5]

O ... é o spread operator — vamos estudá-lo em detalhes no artigo Desestruturação, Spread e Rest Operator.

Tarefa para você

Dado o array abaixo, resolva cada desafio:

const alunos = [
  { nome: "Lucas", nota: 8.5, turma: "A" },
  { nome: "Mariana", nota: 5.0, turma: "B" },
  { nome: "Pedro", nota: 9.0, turma: "A" },
  { nome: "Camila", nota: 4.5, turma: "B" },
  { nome: "Rafael", nota: 7.5, turma: "A" },
];
  1. Filtre apenas os alunos aprovados (nota >= 6)
  2. Extraia apenas os nomes dos alunos da turma "A"
  3. Calcule a média geral de todos os alunos
  4. Desafio extra: retorne os nomes dos alunos aprovados da turma "A", em ordem alfabética
Ver solução — filter, map e reduce sobre os alunos — e o sort que destrói o array
const alunos = [
  { nome: "Lucas", nota: 8.5, turma: "A" },
  { nome: "Mariana", nota: 5.0, turma: "B" },
  { nome: "Pedro", nota: 9.0, turma: "A" },
  { nome: "Camila", nota: 4.5, turma: "B" },
  { nome: "Rafael", nota: 7.5, turma: "A" },
];

// ---------------------------------------------------------------
// 1 — aprovados (nota >= 6)
// ---------------------------------------------------------------
const aprovados = alunos.filter((aluno) => aluno.nota >= 6);
console.log(aprovados.map((a) => a.nome)); // [ 'Lucas', 'Pedro', 'Rafael' ]

// ---------------------------------------------------------------
// 2 — nomes da turma "A"
// ---------------------------------------------------------------
// filter escolhe QUAIS, map escolhe O QUÊ. Trocar a ordem também
// funciona, mas aí o map teria de lidar com quem não é da turma A.
const nomesTurmaA = alunos
  .filter((aluno) => aluno.turma === "A")
  .map((aluno) => aluno.nome);

console.log(nomesTurmaA); // [ 'Lucas', 'Pedro', 'Rafael' ]

// ---------------------------------------------------------------
// 3 — média geral
// ---------------------------------------------------------------
const somaDasNotas = alunos.reduce((soma, aluno) => soma + aluno.nota, 0);
const media = somaDasNotas / alunos.length;

console.log(`Média geral: ${media.toFixed(2)}`); // Média geral: 6.90

// O `0` inicial do reduce não é enfeite: em array vazio, sem ele o
// reduce lança "Reduce of empty array with no initial value".
const vazio = [];
console.log(vazio.reduce((s, a) => s + a.nota, 0)); // 0, sem estourar

// ---------------------------------------------------------------
// 4 — desafio extra: aprovados da turma "A", em ordem alfabética
// ---------------------------------------------------------------
const aprovadosTurmaA = alunos
  .filter((aluno) => aluno.turma === "A" && aluno.nota >= 6)
  .map((aluno) => aluno.nome)
  .sort((a, b) => a.localeCompare(b, "pt-BR"));

console.log(aprovadosTurmaA); // [ 'Lucas', 'Pedro', 'Rafael' ]

// localeCompare em vez do sort() pelado porque o sort padrão compara
// por código de caractere: "Ávila" iria depois de "Zuza", e "banana"
// antes de "Abacaxi".
const comAcento = ["Zuza", "Ávila", "Abacaxi", "banana"];
console.log([...comAcento].sort());
// [ 'Abacaxi', 'Zuza', 'banana', 'Ávila' ] — ordem inútil para português
console.log([...comAcento].sort((a, b) => a.localeCompare(b, "pt-BR")));
// [ 'Abacaxi', 'Ávila', 'banana', 'Zuza' ]

// ---------------------------------------------------------------
// O detalhe que morde: sort ordena NO LUGAR
// ---------------------------------------------------------------
const notas = [8.5, 5, 9, 4.5, 7.5];
const ordenadas = notas.sort((a, b) => a - b);

console.log(ordenadas); // [ 4.5, 5, 7.5, 8.5, 9 ]
console.log(notas);     // [ 4.5, 5, 7.5, 8.5, 9 ] — o original mudou também

// Por isso os exemplos acima usam [...array].sort(): o spread faz a
// cópia, e o original fica intacto. Desde o ES2023 existe toSorted(),
// que já devolve cópia:
const original = [3, 1, 2];
console.log(original.toSorted()); // [ 1, 2, 3 ]
console.log(original);            // [ 3, 1, 2 ] — preservado

sort() e reverse() alteram o array original e ainda o devolvem — parecem funções puras e não são. Ordene sobre [...array] ou use toSorted(). E sort() sem função de comparação ordena como texto: [10, 9, 1].sort()[1, 10, 9].

map, filter e reduce cobrem a maior parte do que se faz com listas, e têm em comum não tocar no array original — exatamente o oposto de push, splice e sort. Saber de cabeça quais métodos mutam e quais devolvem um array novo evita o defeito mais comum com arrays: a lista que muda sozinha porque duas partes do programa apontam para a mesma.

Fontes e Referências

Exercícios

Exercício 1

O sort() sem argumento não faz o que parece. Qual é a saída exata, e como se corrige?

const numeros = [10, 9, 100, 25];
console.log([...numeros].sort());
Ver resposta

✓ Resposta: Sai [10, 100, 25, 9]. Sem função de comparação, o sort() converte cada elemento em texto e ordena alfabeticamente: "10" vem antes de "100", que vem antes de "25", que vem antes de "9". Para números, é obrigatório passar o comparador: [...numeros].sort((a, b) => a - b) devolve [9, 10, 25, 100]. O exemplo do artigo escapa da armadilha só porque todos os seus números têm um dígito.

Exercício 2

O if abaixo não executa, embora o 10 esteja no array. Por quê, e qual método resolve?

const numeros = [10, 20, 30];

if (numeros.indexOf(10)) {
  console.log("Encontrei o 10!");
}
Ver resposta

✓ Resposta: Porque indexOf(10) devolve 0 — o índice, não um sim ou não — e 0 é falso em JavaScript. O elemento foi encontrado justamente na primeira posição, e é essa a que o if descarta. Com indexOf, o teste correto é !== -1. Melhor ainda: if (numeros.includes(10)), que já devolve true ou false e não tem como ser lido errado.

Exercício 3

Depois das duas primeiras linhas, o que cada console.log imprime?

const precos = [100, 250, 80, 400];
const comDesconto = precos.map(p => p * 0.9);

console.log(precos);
console.log(comDesconto);
console.log(precos.filter(p => p > 1000));
Ver resposta

✓ Resposta: [100, 250, 80, 400], [90, 225, 72, 360] e []. O map não altera o array original — ele devolve um novo, e é por isso que precos continua intacto. E o filter que não encontra nada devolve um array vazio, não undefined nem null: isso permite encadear .length ou outro método logo em seguida sem verificar nada antes.

Exercício 4

Percorra o reduce abaixo rodada a rodada e diga o resultado. Depois responda: e se o 0 final virasse 10?

const numeros = [1, 2, 3, 4, 5];
const soma = numeros.reduce((acc, num) => acc + num, 0);
Ver resposta

✓ Resposta: 15. O acumulador parte de 0 e vai somando: 0+1=1, 1+2=3, 3+3=6, 6+4=10, 10+5=15. Trocando o valor inicial por 10, o resultado é 25 — o segundo argumento do reduce é onde o acumulador começa, e ele entra na conta. É por isso que uma soma parte de 0 e um produto teria de partir de 1: o valor inicial precisa ser o elemento neutro da operação.

Exercício 5

Apagaram o primeiro filter do encadeamento. Qual é o novo total, e quem entrou na conta que não deveria?

const funcionarios = [
  { nome: "Ana", salario: 3000, ativo: true },
  { nome: "Carlos", salario: 5000, ativo: false },
  { nome: "Bia", salario: 4000, ativo: true },
  { nome: "Diego", salario: 2500, ativo: true },
];

const totalFolha = funcionarios
  .filter(f => f.salario > 2800)
  .reduce((acc, f) => acc + f.salario, 0);
Ver resposta

✓ Resposta: 12000, contra os 7000 do encadeamento completo. Quem entrou foi o Carlos, que tem ativo: false e ganha 5000 — sem o filtro de ativos, ele passa no critério de salário e é somado. Repare que o programa não acusa nada: a conta continua rodando e devolvendo um número plausível. É a característica dos erros de pipeline — o resultado errado tem a mesma cara do certo, e só uma conferência com valores conhecidos denuncia.

Comentários

Mais em Javascript

O que é Programação Assíncrona? O Event Loop Explicado
O que é Programação Assíncrona? O Event Loop Explicado

Uma linguagem que faz uma coisa de cada vez não deveria conseguir esperar três…

Criando um servidor HTTP com Node.js puro
Criando um servidor HTTP com Node.js puro

Um require de http, uma função de dois argumentos, e o servidor está de pé. O…

Tratamento de Erros com try, catch e finally
Tratamento de Erros com try, catch e finally

Um catch vazio transforma falha em silêncio: a tela não reage, o log não…