Objeto `Array`
Este artigo explica sobre o objeto Array.
Vou explicar usos práticos de arrays passo a passo de forma fácil de entender.
YouTube Video
Objeto Array
O objeto Array do JavaScript é uma das estruturas mais importantes que formam a base de todos os tipos de processamento de dados. Desde operações básicas de arrays até funções de ordem superior úteis para uma transformação eficiente de dados, existem muitos recursos que você deve conhecer.
Noções Básicas de Array
Em JavaScript, arrays são uma estrutura de dados fundamental para lidar com múltiplos valores juntos. Aqui, apresentamos como criar arrays e como ler e escrever seus elementos com exemplos simples.
1// Create arrays in different ways
2const arr1 = [1, 2, 3]; // array literal
3const arr2 = new Array(4, 5, 6); // Array constructor
4const arr3 = Array.of(7, 8, 9); // Array.of
5
6console.log("arr1 created with literal. :", arr1);
7console.log("arr2 created with constructor:", arr2);
8console.log("arr3 created with Array.of. :", arr3);
9// arr1 created with literal. : [ 1, 2, 3 ]
10// arr2 created with constructor: [ 4, 5, 6 ]
11// arr3 created with Array.of. : [ 7, 8, 9 ]
12
13// Access and modify elements
14let first = arr1[0]; // read element
15console.log("First element of arr1:", first);
16// First element of arr1: 1
17
18arr1[1] = 20; // modify element
19console.log("arr1 after modifying index 1:", arr1);
20// arr1 after modifying index 1: [ 1, 20, 3 ]
21
22const len = arr1.length; // get length
23console.log("Length of arr1:", len);
24// Length of arr1: 3
- Este código mostra três maneiras de criar arrays, como ler e atualizar elementos usando índices e como obter o comprimento usando a propriedade
length. - Literais de array são os mais comuns e legíveis, e são usados com mais frequência em situações do dia a dia.
Adicionando e Removendo Elementos (no Final ou Início)
Arrays permitem adicionar ou remover elementos facilmente no final ou início. Essas operações também são úteis ao implementar estruturas como pilhas ou filas.
1// Push and pop (stack-like)
2const stack = [];
3console.log("Initial stack:", stack);
4
5stack.push(1); // push 1
6console.log("After push(1):", stack);
7
8stack.push(2); // push 2
9console.log("After push(2):", stack);
10
11const last = stack.pop(); // pop -> 2
12console.log("Popped value:", last);
13console.log("Stack after pop():", stack);
14
15// Unshift and shift (queue-like)
16const queue = [];
17console.log("Initial queue:", queue);
18
19queue.push('a'); // add to end
20console.log("After push('a'):", queue);
21
22queue.unshift('start'); // add to front
23console.log("After unshift('start'):", queue);
24
25const firstItem = queue.shift(); // remove from front
26console.log("Shifted value:", firstItem);
27console.log("Queue after shift():", queue);
28
29// Initial stack: []
30// After push(1): [ 1 ]
31// After push(2): [ 1, 2 ]
32// Popped value: 2
33// Stack after pop(): [ 1 ]
34
35// Initial queue: []
36// After push('a'): [ 'a' ]
37// After unshift('start'): [ 'start', 'a' ]
38// Shifted value: start
39// Queue after shift(): [ 'a' ]
pushepopoperam no final de um array. Eles são ideais para implementar estruturas de pilha.unshifteshiftoperam no início de um array. No entanto, esteja ciente de que operar no início requer o deslocamento de todos os índices dos elementos internamente, o que torna esta uma operação custosa.
Manipulando Elementos no Meio (splice e slice)
Ao manipular elementos no meio de um array, escolha entre splice e slice dependendo se você quer modificar o array original ou não. Se você só quer extrair parte de um array, use slice; se quiser modificar o próprio array, como inserir ou remover elementos, use splice.
1// slice (non-destructive)
2const nums = [0, 1, 2, 3, 4];
3console.log("Original nums:", nums);
4
5const part = nums.slice(1, 4); // returns [1, 2, 3]
6console.log("Result of nums.slice(1, 4):", part);
7console.log("nums after slice (unchanged):", nums);
8
9// splice (destructive)
10const arr = [10, 20, 30, 40];
11console.log("\nOriginal arr:", arr);
12
13// remove 1 item at index 2, insert 25 and 27
14arr.splice(2, 1, 25, 27);
15console.log("After arr.splice(2, 1, 25, 27):", arr);
16
17// Original nums: [ 0, 1, 2, 3, 4 ]
18// Result of nums.slice(1, 4): [ 1, 2, 3 ]
19// nums after slice (unchanged): [ 0, 1, 2, 3, 4 ]
20
21// Original arr: [ 10, 20, 30, 40 ]
22// After arr.splice(2, 1, 25, 27): [ 10, 20, 25, 27, 40 ]
sliceapenas extrai elementos e não modifica o array original.spliceadiciona ou remove elementos e modifica o próprio array, portanto tenha especial atenção ao seu impacto no comportamento.
Iteração (for / for...of / forEach)
Existem várias maneiras de processar arrays em sequência, e você pode escolher conforme o objetivo e a preferência de estilo de codificação. Aqui estão três estruturas de loop típicas.
1const items = ['apple', 'banana', 'cherry'];
2console.log("Items:", items);
3
4// classic for
5console.log("\n--- Classic for loop ---");
6for (let i = 0; i < items.length; i++) {
7 console.log(`Index: ${i}, Value: ${items[i]}`);
8}
9
10// for...of (recommended for values)
11console.log("\n--- for...of loop ---");
12for (const item of items) {
13 console.log(`Value: ${item}`);
14}
15
16// forEach (functional style)
17console.log("\n--- forEach loop ---");
18items.forEach((item, index) => {
19 console.log(`Index: ${index}, Value: ${item}`);
20});- O loop
foré o mais flexível, permitindo operações com índices e controle detalhado sobre a iteração utilizandobreake outros comandos. for...offornece uma maneira concisa de lidar com os valores dos elementos e é o mais equilibrado em termos de legibilidade.forEachpermite um código de estilo funcional e é bem adequado para operações com efeitos colaterais, como registrar logs ou atualizar dados para cada elemento. No entanto, observe que você não pode usarbreakoucontinue, e não é adequado para processamento assíncrono comawait.
map / filter / reduce — Funções de Ordem Superior
map, filter e reduce são funções de ordem superior frequentemente utilizadas ao transformar, filtrar ou agregar arrays. Como você pode expressar claramente processamentos repetitivos, seu código se torna simples e fácil de entender.
1const numbers = [1, 2, 3, 4, 5];
2console.log("Original numbers:", numbers);
3
4// map: transform each item
5const doubled = numbers.map(n => n * 2);
6console.log("\nResult of map (n * 2):", doubled);
7
8// filter: select items
9const evens = numbers.filter(n => n % 2 === 0);
10console.log("Result of filter (even numbers):", evens);
11
12// reduce: accumulate to single value
13const sum = numbers.reduce((acc, n) => acc + n, 0);
14console.log("Result of reduce (sum):", sum);
15
16// Original numbers: [ 1, 2, 3, 4, 5 ]
17// Result of map (n * 2): [ 2, 4, 6, 8, 10 ]
18// Result of filter (even numbers): [ 2, 4 ]
19// Result of reduce (sum): 15
- Esses métodos permitem que você foque no que deseja fazer de forma declarativa, melhorando a legibilidade e evitando efeitos colaterais indesejados.
find / findIndex / some / every
Aqui está uma visão geral dos métodos de busca e verificação de condições. Eles são úteis para encontrar elementos que atendam a determinadas condições ou realizar verificações booleanas no conjunto.
1const users = [
2 { id: 1, name: 'Alice' },
3 { id: 2, name: 'Bob' },
4 { id: 3, name: 'Carol' }
5];
6
7console.log("Users:", users);
8
9// Find the first user whose name is 'Bob'
10const bob = users.find(user => user.name === 'Bob');
11console.log("\nResult of find (name === 'Bob'):", bob);
12
13// Find index of the user whose id is 3
14const indexOfId3 = users.findIndex(user => user.id === 3);
15console.log("Result of findIndex (id === 3):", indexOfId3);
16
17// Check if there exists a user with id = 2
18const hasId2 = users.some(user => user.id === 2);
19console.log("Result of some (id === 2):", hasId2);
20
21// Check if all users have a numeric id
22const allHaveNumericId = users.every(user => typeof user.id === 'number');
23console.log("Result of every (id is number):", allHaveNumericId);
24// Result of find (name === 'Bob'): { id: 2, name: 'Bob' }
25// Result of findIndex (id === 3): 2
26// Result of some (id === 2): true
27// Result of every (id is number): true
findretorna o primeiro elemento que corresponde à condição.findIndexretorna o índice do elemento que corresponde à condição.someretornatruese houver pelo menos um elemento que satisfaça a condição.everyretornatruese todos os elementos satisfizerem a condição.
Todos esses métodos são muito úteis no processamento de arrays, então usá-los adequadamente conforme a situação manterá seu código conciso e claro.
Funções de Ordenação e Comparação
Arrays são ordenados usando sort, mas por padrão ele compara os elementos como strings, o que pode levar a resultados inesperados ao ordenar números.
1const nums = [10, 2, 33, 4];
2console.log("Original nums:", nums);
3
4// Default sort: compares elements as strings (not suitable for numbers)
5nums.sort();
6console.log("\nAfter default sort (string comparison):", nums);
7
8// Numeric ascending sort using a compare function
9nums.sort((a, b) => a - b);
10console.log("After numeric sort (a - b):", nums);
11
12// Sort objects by a property
13const people = [{ age: 30 }, { age: 20 }, { age: 25 }];
14console.log("\nOriginal people:", people);
15
16people.sort((a, b) => a.age - b.age);
17console.log("After sorting people by age:", people);
18
19// After default sort (string comparison): [ 10, 2, 33, 4 ]
20// After numeric sort (a - b): [ 2, 4, 10, 33 ]
21// Original people: [ { age: 30 }, { age: 20 }, { age: 25 } ]
22// After sorting people by age: [ { age: 20 }, { age: 25 }, { age: 30 } ]
- Ao ordenar números ou objetos, sempre especifique uma função de comparação para ordená-los na ordem desejada.
- Em uma função de comparação, um valor de retorno negativo coloca
aantes deb, um valor positivo colocabantes dea, e 0 mantém suas posições inalteradas.
Cópia e Imutabilidade de Arrays
Ao copiar arrays, é importante compreender a diferença entre 'cópia por referência' e 'cópia superficial.'. Em particular, esteja ciente de que, se houver objetos dentro do array, uma cópia superficial fará com que os objetos internos sejam compartilhados.
Cópia por referência
Quando você atribui um array a outra variável, o array em si não é duplicado; ao invés disso, é copiada a 'referência' que aponta para o mesmo array.
1const a = [1, 2, 3];
2console.log("Original a:", a);
3
4// Reference copy; modifying b also affects a
5const b = a;
6console.log("\nReference copy b = a:", b);
7// Reference copy b = a: [ 1, 2, 3 ]
8
9b[0] = 100;
10console.log("After modifying b[0] = 100:");
11console.log("a:", a); // a: [ 100, 2, 3 ] (affected)
12console.log("b:", b); // b: [ 100, 2, 3 ]
- Com a cópia por referência, se você modificar o conteúdo do array usando a variável copiada, essas alterações também serão refletidas na variável original que se refere ao mesmo array.
Cópia superficial
Usar slice() ou a sintaxe spread cria uma 'cópia superficial', pois apenas os valores são duplicados; os arrays original e copiado são tratados como entidades separadas.
1const a = [1, 2, 3];
2console.log("Original a:", a);
3
4// Shallow copy (new array)
5const b = a.slice();
6console.log("\nShallow copy using slice():", b);
7// Shallow copy using slice(): [ 100, 2, 3 ]
8
9const c = [...a];
10console.log("Shallow copy using spread [...a]:", c);
11// Shallow copy using spread [...a]: [ 100, 2, 3 ]
12
13// Modifying c or d does NOT affect a
14b[1] = 200;
15c[2] = 300;
16console.log("\nAfter modifying b[1] = 200 and c[2] = 300:");
17console.log("a:", a); // [ 100, 2, 3 ]
18console.log("b:", b); // [ 100, 200, 3 ]
19console.log("c:", c); // [ 100, 2, 300 ]
- Este código demonstra que uma cópia superficial de um array criada por
slice()ou pela sintaxe spread não afeta o array original.
Cópia superficial e imutabilidade
Mesmo que você duplique um array usando uma 'cópia superficial', pode ocorrer compartilhamento involuntário se o array contiver objetos em seu interior.
1// Shallow copy doesn't clone inner objects
2const nested = [{ x: 1 }, { x: 2 }];
3console.log("\nOriginal nested:", nested);
4// Original nested: [ { x: 1 }, { x: 2 } ]
5
6const shallow = nested.slice();
7console.log("Shallow copy of nested:", shallow);
8// Shallow copy of nested: [ { x: 1 }, { x: 2 } ]
9
10// Changing inner object affects both arrays
11shallow[0].x = 99;
12console.log("\nAfter shallow[0].x = 99:");
13console.log("nested:", nested);
14console.log("shallow:", shallow);
15// nested: [ { x: 99 }, { x: 2 } ]
16// shallow: [ { x: 99 }, { x: 2 } ]
- Este código demonstra que com uma cópia superficial, os objetos internos são compartilhados, então modificar esses objetos internos afeta tanto o array original quanto a cópia.
- Se você precisa de dados independentes, é necessário fazer uma 'cópia profunda', como usando
structuredClone()ou conversão para JSON.
Métodos utilitários úteis
Os seguintes são métodos utilitários frequentemente usados ao trabalhar com arrays. Usando-os adequadamente para o seu propósito, você pode escrever um código curto e legível.
includes
O método includes verifica se um valor específico está incluído em um array.
1// includes: check if array contains a value
2const letters = ['a', 'b', 'c'];
3const hasB = letters.includes('b');
4console.log("letters:", letters); // [ 'a', 'b', 'c' ]
5console.log("letters.includes('b'):", hasB); // true
- Neste código, o método
includesé utilizado para determinar de forma concisa se um valor específico existe dentro do array.
concat
O método concat retorna um novo array que adiciona ao final o array ou os valores especificados, mantendo o array original inalterado.
1// concat: merge arrays without mutation
2const a1 = [1, 2];
3const a2 = [3, 4];
4const combined = a1.concat(a2);
5console.log("a1.concat(a2):", combined); // [ 1, 2, 3, 4 ]
6console.log("a1(unchanged):", a1); // [ 1, 2 ]
- Este código mostra que
concatnão é destrutivo, permitindo gerar um novo array enquanto preserva o original.
flat
Utilizando o método flat, é possível achatar arrays aninhados.
1// flat and flatMap: flatten arrays or map + flatten in one step
2const nested = [1, [2, [3]]];
3console.log("nested:", nested); // nested: [ 1, [ 2, [ 3 ] ] ]
4console.log("nested.flat():", nested.flat()); // default depth = 1
5// nested.flat(): [ 1, 2, [ 3 ] ]
- Este código demonstra o resultado de achatar um array em um nível.
- Como
flatpermite especificar a profundidade, é possível resolver o aninhamento de forma flexível conforme necessário.
flatMap
O método flatMap aplica uma transformação a cada elemento e depois achata automaticamente os resultados em um array unidimensional.
1// flat and flatMap: flatten arrays or map + flatten in one step
2const words = ['hello world', 'hi'];
3console.log("words:", words); // [ 'hello world', 'hi' ]
4
5const splitWords = words.flatMap(w => w.split(' '));
6console.log("words.flatMap(w => w.split(' ')):", splitWords);
7// words.flatMap(w => w.split(' ')): [ 'hello', 'world', 'hi' ]
- Este código mostra um exemplo em que cada string em um array é dividida por espaços, e os resultados são combinados e achatados em um único array.
join
O método join cria uma string ao concatenar os elementos de um array com um separador especificado.
1// join: combine elements into a string with a separator
2const csv = ['a', 'b', 'c'].join(',');
3console.log("['a', 'b', 'c'].join(','):", csv); // a,b,c
- Neste código, o método
joiné utilizado para converter um array em uma string separada por vírgulas.
Armadilhas Comuns
As operações de arrays podem parecer simples à primeira vista, mas há vários pontos que podem facilmente levar a comportamentos indesejados. Muitas armadilhas passam despercebidas ao lidar com arrays no dia a dia, então lembrar dos pontos a seguir irá melhorar bastante a confiabilidade de seu código.
sort()doArrayfaz a ordenação por comparação de strings por padrão. Para ordenar números corretamente, você deve sempre fornecer uma função de comparação.- Copiar arrays (via
sliceou sintaxe spread, etc.) cria uma cópia superficial. Se seus arrays contiverem objetos, tenha cuidado porque os dados originais podem ser alterados involuntariamente. spliceé um método destrutivo que altera diretamente o array, enquantosliceé um método não destrutivo que não modifica o array original. É importante usá-los adequadamente para suas necessidades.forEachnão é adequado para loops com processamento assíncrono usandoawait. Se você quer garantir a execução ordenada de processamento assíncrono, recomenda-se usarfor...of.
Exemplo Prático
Abaixo está um exemplo que combina métodos de array para 'obter a idade total dos dados do usuário, extrair aqueles que têm 30 anos ou mais e criar uma lista de nomes.'.
1const users = [
2 { name: 'Alice', age: 28 },
3 { name: 'Bob', age: 34 },
4 { name: 'Carol', age: 41 },
5 { name: 'Dave', age: 19 }
6];
7
8console.log("Users:", users);
9
10// sum ages
11const totalAge = users.reduce((acc, u) => acc + u.age, 0);
12console.log("\nTotal age of all users:", totalAge);
13// Total age of all users: 122
14
15// filter and map names of users 30 and older
16const namesOver30 = users
17 .filter(u => u.age >= 30)
18 .map(u => u.name);
19
20console.log("Users aged 30 or older (names):", namesOver30);
21// Users aged 30 or older (names): [ 'Bob', 'Carol' ]
- Encadeando
reduce,filteremap, você pode facilmente escrever agregação, extração condicional e transformação de dados. - Esse tipo de 'pipeline de processamento de dados' é altamente legível e, como um estilo com poucos efeitos colaterais, é muito usado em aplicações reais.
Resumo
Com arrays em JavaScript, mesmo operações básicas têm ampla aplicação, e ao usar funções de ordem superior, seu código se torna mais conciso e expressivo. Há muitos pontos para entender, mas uma vez que você dominar o uso apropriado de cada um eles, o processamento de dados ficará muito mais fluido.
Você pode acompanhar o artigo acima usando o Visual Studio Code em nosso canal do YouTube. Por favor, confira também o canal do YouTube.