Dia 8 — Arrays
Criar, acessar e alterar arrays
Criar e alterar array: push, pop, splice
Array é a lista ordenada: valores guardados por posição, numerada a partir de 0, e o tamanho mora em .length. A forma literal é [], e length é contagem de elementos — não é o maior índice. Uma lista de três itens tem length 3 e último índice 2.
O primeiro elemento é o índice 0 e o último é length - 1. Array vazio é [], com length 0 — e pop, shift e at em lista vazia devolvem undefined, sem erro.
const frutas = ['maçã', 'banana', 'laranja']; frutas[0] // 'maçã' — o primeiro elemento frutas[2] // 'laranja' — o último elemento, length - 1 frutas.at(-1) // 'laranja' — o mesmo, contando do fim frutas.length // 3 const vazia = []; vazia.pop() // undefined — não quebra
Índice fora da lista devolve undefined e não quebra o programa. Atribuir em índice maior que o length cria buraco: [1, , 3], que tem length 3 mas só dois elementos — o do meio não existe. Esse array se chama sparse: os buracos não são undefined, são posições vazias, e método como forEach pula por cima delas em vez de passar.
| Método | Assinatura | Efeito |
|---|---|---|
push | push(...itens): number | acrescenta no fim, devolve o novo length |
pop | pop() | remove e devolve o último |
shift | shift() | remove e devolve o primeiro |
unshift | unshift(...itens): number | insere no início, devolve o novo length |
splice | splice(inicio, quantos, ...novos): array | remove e insere em qualquer ponto |
at | at(indice) | acessa contando do fim: at(-1) é o último |
concat | concat(...outros): array | devolve lista nova com as duas juntas |
join | join(separador): string | transforma a lista em texto |
As duas pontas custam caro
push e pop trabalham no fim: o JavaScript não precisa mover nada. shift e unshift trabalham no início: todo elemento é deslocado uma posição, e em lista grande isso pesa. unshift paga mais ainda, porque o motor precisa abrir espaço para o novo item e só depois preenchê-lo. Quando o custo importa, use push e deixe a ordem para o fim.
splice é o único dos sete que aceita o índice do meio, e é o único dos quatro de alterar que devolve os elementos removidos, não os inseridos.
const letras = ['a', 'b', 'c', 'd']; letras.splice(1, 2, 'X'); // ['a', 'X', 'd']
const não protege o conteúdo
const impede reatribuar a variável, não mexer dentro dela. Lista declarada com const pode ganhar e perder elementos à vontade; o que não compila é frutas = [...]. A mesma regra vale para objeto, e o congelamento de verdade é Object.freeze (dia 9).
Os verbos do dia são adicionar e remover, e cada ponta da lista tem o seu par: push e pop no fim, unshift e shift no início, e splice em qualquer ponto.
const notas = [8, 9]; notas.push(10); // permitido // notas = []; // TypeError: Assignment to constant variable
Cópia de verdade é [...notas] ou notas.slice(): os dois criam lista nova. Atribuir const copia = notas não copia nada — as duas variáveis apontam para a mesma lista, e mexer em uma muda a outra.
lengthé a única propriedade do array que é gravável, e escrever nela trunca ou estende:notas.length = 1joga fora tudo depois do primeiro item;notas.length = 3cria buracos, comundefinedno lugar do que não foi preenchido. Nenhum dos dois avisa.
Exemplo
'use strict'; // Exemplo: criar, ler e alterar um array, e onde cada metodo custa caro. const frutas = ['maçã', 'banana', 'laranja']; console.log('lista:', frutas.join(', ')); console.log('length e 3 e o ultimo indice e 2:', frutas.length, frutas.length - 1); console.log('indice 0:', frutas[0], '| indice 2:', frutas[2]); // Indice fora da lista devolve undefined e nao quebra nada. console.log('indice 99 (nao existe):', frutas[99]); // Atribuir em indice grande cria buraco: o elemento do meio nao existe. const esparsa = [1, , 3]; console.log('esparsa.length:', esparsa.length, '| esparsa:', esparsa.map((v) => String(v)).join(' ')); // As duas pontas: push e pop sao baratos, shift e unshift remexem em tudo. const itens = ['b', 'c']; itens.push('d'); console.log('depois de push:', itens.join(', '), '| length', itens.length); console.log('pop devolveu:', itens.pop(), '| sobrou:', itens.join(', ')); console.log('unshift devolve o novo length:', itens.unshift('a'), '| lista:', itens.join(', ')); console.log('shift devolveu:', itens.shift(), '| sobrou:', itens.join(', ')); // splice mexe no meio e devolve o que REMOVEU, nao o que inseriu. const letras = ['a', 'b', 'c', 'd']; const removidas = letras.splice(1, 2, 'X'); console.log('splice(1, 2, "X") ->', letras.join(', '), '| devolveu as removidas:', removidas.join(', ')); // splice com quantos 0 so insere, sem apagar ninguem. const numeros = [1, 2, 3]; numeros.splice(1, 0, 1.5); console.log('splice(1, 0, 1.5) ->', numeros.join(', ')); // at() conta a partir do fim; o indice -1 nao existe de outra forma. console.log('at(-1) e o ultimo:', letras.at(-1), '| at(0):', letras.at(0)); // const trava a variavel, nao o conteudo. E o push passa. const notas = [8, 9]; notas.push(10); console.log('const com push:', notas.join(', ')); try { notas = []; } catch (erro) { console.log(erro.constructor.name + ': ' + erro.message); } // Copia de verdade: spread ou slice. Atribuicao nao copia. const original = [1, 2, 3]; const copia = [...original]; copia.push(4); console.log('original intacto:', original.join(', '), '| copia:', copia.join(', ')); const mesma = original; mesma.push(99); console.log('as duas apontam para a mesma lista:', original.join(', ')); // length e gravavel: escrever nela trunca ou preenche com buraco. const trunca = [1, 2, 3, 4, 5]; trunca.length = 2; console.log('length = 2 trunca:', trunca.join(', ')); const estende = [1]; estende.length = 3; console.log('length = 3 estende:', estende.map((v) => String(v)).join(', ')); // Array de uma linha, sem const, ainda e um array. console.log('literal direto:', ['a', 'b'].join('-')); // concat devolve lista nova, sem mexer nas originais. const a = [1, 2]; const b = [3, 4]; const juntas = a.concat(b, [5]); console.log('concat:', juntas.join(', '), '| a continua', a.length, '| b continua', b.length);
Saída real
lista: maçã, banana, laranja length e 3 e o ultimo indice e 2: 3 2 indice 0: maçã | indice 2: laranja indice 99 (nao existe): undefined esparsa.length: 3 | esparsa: 1 3 depois de push: b, c, d | length 3 pop devolveu: d | sobrou: b, c unshift devolve o novo length: 3 | lista: a, b, c shift devolveu: a | sobrou: b, c splice(1, 2, "X") -> a, X, d | devolveu as removidas: b, c splice(1, 0, 1.5) -> 1, 1.5, 2, 3 at(-1) e o ultimo: d | at(0): a const com push: 8, 9, 10 TypeError: Assignment to constant variable. original intacto: 1, 2, 3 | copia: 1, 2, 3, 4 as duas apontam para a mesma lista: 1, 2, 3, 99 length = 2 trunca: 1, 2 length = 3 estende: 1, , literal direto: a-b concat: 1, 2, 3, 4, 5 | a continua 2 | b continua 2
Iterar, transformar e filtrar arrays
map, filter e reduce: transformar e filtrar array
O for...of percorre; estes métodos devolvem algo. É a diferença que decide o código: quando o método entrega resultado novo, encadeia; quando só serve para efeito colateral, o retorno é undefined e o resultado se joga fora.
| Método | Assinatura | Devolve |
|---|---|---|
forEach | forEach(callback): undefined | nada, só executa |
map | map(callback): array | array do mesmo tamanho, com o valor trocado |
filter | filter(callback): array | só os que passaram no teste |
find | find(callback) | o primeiro que passou, ou undefined |
findIndex | findIndex(callback): number | o índice do primeiro, ou -1 |
some | some(callback): boolean | true se algum passou |
every | every(callback): boolean | true se todos passaram |
reduce | reduce(callback, inicial): qualquer | um valor só, acumulado |
sort | sort(comparador): array | a mesma lista, ordenada |
reverse | reverse(): array | a mesma lista, ao contrário |
slice | slice(inicio, fim): array | trecho, em lista nova |
flat | flat(profundidade): array | achata listas dentro de listas |
Os dois que devolvem array do mesmo tamanho
map e filter recebem o mesmo tipo de callback e são fáceis de confundir. A regra é o nome: map transforma cada elemento e mantém a quantidade; filter escolhe elementos e muda a quantidade.
const numeros = [1, 2, 3, 4]; numeros.map((n) => n * 10); // [10, 20, 30, 40] — 4 itens numeros.filter((n) => n > 2); // [3, 4] — 2 itens
map sobre objeto devolve array de valores, sem a chave: quem precisa do par usa Object.entries(obj).map(([chave, valor]) => ...).
reduce é o resto com dois parâmetros
O callback de reduce recebe o acumulado e o item: (acumulado, item) => novoAcumulado, e devolve o novo acumulado. O segundo argumento de reduce é o valor inicial.
const notas = [8, 9, 7]; notas.reduce((soma, nota) => soma + nota, 0); // 24
Sem valor inicial, o acumulado começa no primeiro elemento — daí a necessidade do 0 ao somar lista de número. Com número a soma ainda sai certa por sorte; com objeto, o primeiro registro se mistura no resultado, porque o acumulado já começou sendo o próprio item e não um objeto vazio. Com 0, '', [] ou {} como segundo argumento, a base é limpa, e a soma de array vazio devolve 0 em vez de explodir.
reduceRight faz o mesmo da direita para a esquerda. Serve para cortar texto em dobras, ou para não estourar a pilha quando o array é enorme e a operação cria array novo a cada passo.
Juntar, cortar e copiar
concat junta: a.concat(b) devolve lista nova com as duas, e a.concat(b, c) aceita várias de uma vez. Não altera nenhuma das duas. Para juntar várias listas de uma vez, o caminho curto é o spread em array — concat(...listas), ou o que o material chama de ...array, que é [...a, ...b]: o mesmo operador do dia 7 com outro papel.
splice (dia 8, aula 1) altera a lista original, tirando e inserindo; slice é o que só entrega um trecho, em lista nova, sem tocar no original. A cópia de array de verdade é [...lista] ou lista.slice(): os dois criam lista nova, e atribuir const copia = lista não copia nada — as duas variáveis apontam para a mesma lista.
const a = [1, 2]; const b = [3, 4]; a.concat(b); // [1, 2, 3, 4] [...a, ...b]; // [1, 2, 3, 4] — o mesmo resultado a.slice(0, 1); // [1] — trecho, a lista segue com 2 itens
Ordenar sem quebrar
sort com um único argumento ordena como texto: [10, 9, 1].sort() devolve [1, 10, 9]. Com números, sempre comparador: (a, b) => a - b para crescente, (b - a) => b - a para decrescente. O comparador devolve um número — negativo, zero ou positivo — e o sort reordena a lista original.
Para não mexer no original, o caminho curto é [...lista].sort(...) ou lista.slice().sort(...).
finddevolve o elemento efindIndexdevolve o índice. Trocar os dois troca o resultado inteiro do código, porque objeto é o que ofinddevolve e0é o que ofindIndexdevolve para o primeiro item — e0é falsy (dia 5), entãoif (lista.findIndex(...))falha justamente no achado. Use!== -1, ousome, que devolveboolean.
Exemplo
'use strict'; // Exemplo: os metodos que devolvem array, os que devolvem valor e os // que so executam. A diferenca e o que separa map de filter. const numeros = [1, 2, 3, 4, 5]; // forEach executa e devolve undefined: o retorno se joga fora. console.log('forEach devolve:', numeros.forEach((n) => n * 2)); // map transforma cada elemento e MANTEM a quantidade. console.log('map (5 itens):', numeros.map((n) => n * 10).join(', ')); // filter escolhe elementos e MUDA a quantidade. console.log('filter (2 itens):', numeros.filter((n) => n > 3).join(', ')); // Encadeamento: cada um alimenta o seguinte, porque cada um devolve array. const dobradosPares = numeros .map((n) => n * 2) .filter((n) => n % 4 === 0); console.log('map + filter encadeados:', dobradosPares.join(', ')); // find devolve o ELEMENTO; findIndex devolve o INDICE. const notas = [ { aluno: 'Ana', nota: 6 }, { aluno: 'Bia', nota: 9 }, { aluno: 'Cau', nota: 7 }, ]; const reprovado = notas.find((n) => n.nota < 7); const indiceReprovado = notas.findIndex((n) => n.nota < 7); console.log('find devolve o objeto:', reprovado.aluno, reprovado.nota); console.log('findIndex devolve o indice:', indiceReprovado, 'ou -1:', notas.findIndex((n) => n.nota > 100)); // A armadilha: o indice 0 e falsy, entao o if abaixo nao entra. if (notas.findIndex((n) => n.nota < 7)) { console.log('isto nao imprime: achou no indice 0, que e falsy'); } if (notas.findIndex((n) => n.nota < 7) !== -1) { console.log('isto imprime: a comparacao com -1 e a forma correta'); } console.log('some devolve boolean, sem esse problema:', notas.some((n) => n.nota < 7)); // some responde "existe algum"; every responde "todos". console.log('some (>=7):', notas.some((n) => n.nota >= 7), '| some (>=10):', notas.some((n) => n.nota >= 10)); console.log('every (>=6):', notas.every((n) => n.nota >= 6), '| every (>=8):', notas.every((n) => n.nota >= 8)); // reduce junta tudo num valor so. O segundo argumento e o valor inicial. console.log('reduce soma:', numeros.reduce((soma, n) => soma + n, 0)); // Sem valor inicial, o acumulado comeca no PRIMEIRO ELEMENTO: comeca sendo // o proprio registro, nao um objeto vazio. O que o codigo escreve nele se // acumula em cima da chave que ja existia. const sujo = notas.reduce((acc, n) => { acc[n.aluna] = n.nota; return acc; }); console.log('reduce sem inicial: o registro original sobrevive no resultado ->'); console.log(' aluno:', sujo.aluno, '| nota:', sujo.nota, '| [undefined]:', sujo[undefined]); console.log('reduce com objeto, base {} limpa:', notas.reduce((acc, n) => { acc[n.aluno] = n.nota; return acc; }, {})); // reduceRight faz a mesma coisa de tras para frente. const palavras = ['e', 'a', 'volta']; console.log('reduceRight:', palavras.reduceRight((acc, p) => acc + p + ' ', '').trim()); // sort sem comparador ordena como texto, e 10 vem antes de 9. const embaralhado = [10, 9, 1, 100]; console.log('sort sem comparador:', embaralhado.slice().sort().join(', ')); console.log('sort com (a, b) => a - b:', embaralhado.slice().sort((a, b) => a - b).join(', ')); console.log('sort decrescente:', embaralhado.slice().sort((a, b) => b - a).join(', ')); // reverse e slice mexem na lista, mas slice devolve uma nova. const letras = ['a', 'b', 'c', 'd']; const copia = letras.slice(1, 3); copia.reverse(); console.log('slice(1, 3):', copia.join(', '), '| o original intacto:', letras.join(', ')); // flat achata listas dentro de listas; flatMap achata um nivel so. const agrupado = [[1, 2], [3, 4]]; console.log('flat:', agrupado.flat().join(', ')); console.log('flat(1):', [[1, [2]], 3].flat(1).join(', ')); console.log('flatMap:', agrupado.flatMap((par) => par).join(', ')); // A tabela de uma linha que o professor sempre pede: nome e media. const alunos = [ { nome: 'Ana', notas: [8, 9, 7] }, { nome: 'Bia', notas: [5, 6, 7] }, { nome: 'Cau', notas: [4, 3, 9] }, ]; const relatorio = alunos .map((aluno) => ({ nome: aluno.nome, media: +(aluno.notas.reduce((s, n) => s + n, 0) / aluno.notas.length).toFixed(2), })) .filter((aluno) => aluno.media >= 6); console.log('aprovados:', relatorio.map((a) => a.nome + ' (' + a.media + ')').join(', ')); console.log('quantos aprovaram:', relatorio.length, 'de', alunos.length);
Saída real
forEach devolve: undefined
map (5 itens): 10, 20, 30, 40, 50
filter (2 itens): 4, 5
map + filter encadeados: 4, 8
find devolve o objeto: Ana 6
findIndex devolve o indice: 0 ou -1: -1
isto imprime: a comparacao com -1 e a forma correta
some devolve boolean, sem esse problema: true
some (>=7): true | some (>=10): false
every (>=6): true | every (>=8): false
reduce soma: 15
reduce sem inicial: o registro original sobrevive no resultado ->
aluno: Ana | nota: 6 | [undefined]: 7
reduce com objeto, base {} limpa: { Ana: 6, Bia: 9, Cau: 7 }
reduceRight: volta a e
sort sem comparador: 1, 10, 100, 9
sort com (a, b) => a - b: 1, 9, 10, 100
sort decrescente: 100, 10, 9, 1
slice(1, 3): c, b | o original intacto: a, b, c, d
flat: 1, 2, 3, 4
flat(1): 1, 2, 3
flatMap: 1, 2, 3, 4
aprovados: Ana (8), Bia (6)
quantos aprovaram: 2 de 3