Arrays: criando, acessando e iterando

1. Fundamentos da Criação de Arrays

Literais vs. Construtor new Array()

A forma mais comum e recomendada de criar arrays em JavaScript é usando literais:

// Literal (recomendado)
const frutas = ['maçã', 'banana', 'laranja'];
const numeros = [1, 2, 3, 4, 5];
const misto = [1, 'texto', true, { nome: 'João' }];

O construtor new Array() deve ser usado com cautela:

// Construtor - comportamentos inesperados
const arr1 = new Array(5);        // Cria array esparso com 5 slots vazios
const arr2 = new Array(5, 10);    // Cria [5, 10]
const arr3 = new Array('5');      // Cria ['5']

// Literal equivalente
const arr1Literal = [5];          // Cria [5]

Array.from() e Array.of() – Casos de Uso Modernos

Array.from() é excelente para converter objetos iteráveis ou array-like em arrays reais:

// Convertendo NodeList (DOM) em array
const elementos = document.querySelectorAll('div');
const arrayElementos = Array.from(elementos);

// Usando função de mapeamento
const quadrados = Array.from([1, 2, 3, 4], x => x * x);
// Resultado: [1, 4, 9, 16]

// Criando sequência numérica
const sequencia = Array.from({ length: 5 }, (_, i) => i + 1);
// Resultado: [1, 2, 3, 4, 5]

Array.of() resolve a ambiguidade do construtor:

const arr = Array.of(5);          // [5] - sempre cria array com elementos
const arr2 = Array.of(5, 10, 15); // [5, 10, 15]

Arrays Esparsos e Array(length) – Armadilhas Comuns

Arrays esparsos têm "buracos" que podem causar comportamentos inesperados:

const esparso = [1, , 3];         // [1, empty, 3]
console.log(esparso[1]);          // undefined
console.log(esparso.length);      // 3

// Armadilhas com métodos de iteração
esparso.forEach(item => console.log(item)); // Só imprime 1 e 3 (pula o buraco)
const mapeado = esparso.map(x => x * 2);    // [2, empty, 6]

2. Acessando Elementos e Propriedades Essenciais

Índices Numéricos e Notação de Colchetes

const cores = ['vermelho', 'azul', 'verde'];
console.log(cores[0]);  // 'vermelho'
console.log(cores[2]);  // 'verde'

// Índices negativos não funcionam com colchetes
console.log(cores[-1]); // undefined

Propriedade length – Comportamento Dinâmico e Mutação

const arr = [1, 2, 3];
console.log(arr.length); // 3

// Reduzir length remove elementos
arr.length = 2;
console.log(arr); // [1, 2]

// Aumentar length cria espaços vazios
arr.length = 5;
console.log(arr); // [1, 2, empty × 3]

Acessando o Último Elemento com at() (ES2022)

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

// Método tradicional (menos legível)
console.log(numeros[numeros.length - 1]); // 50

// Método moderno com at()
console.log(numeros.at(-1));  // 50
console.log(numeros.at(-2));  // 40
console.log(numeros.at(0));   // 10

3. Iteração Clássica: Loops Tradicionais

for Clássico e for...in

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

// for clássico - controle total
for (let i = 0; i < frutas.length; i++) {
  console.log(frutas[i]);
}

// for...in - NÃO recomendado para arrays (itera sobre propriedades)
for (let indice in frutas) {
  console.log(frutas[indice]); // Funciona, mas pode incluir propriedades herdadas
}

while e do...while para Controle de Fluxo

const dados = [1, 2, 3, 4, 5];
let i = 0;

// while - útil quando não sabemos o número exato de iterações
while (i < dados.length) {
  console.log(dados[i]);
  i++;
}

// do...while - executa pelo menos uma vez
let j = 0;
do {
  console.log(dados[j]);
  j++;
} while (j < dados.length);

Otimização de Desempenho com Cache de length

// Sem cache - acessa length a cada iteração
for (let i = 0; i < arrayGrande.length; i++) {
  // processamento
}

// Com cache - melhor performance
const len = arrayGrande.length;
for (let i = 0; i < len; i++) {
  // processamento
}

4. Iteração Moderna: Métodos de Array Nativos

forEach() – Iteração Funcional e Limitações

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

numeros.forEach((valor, indice, array) => {
  console.log(`Índice ${indice}: ${valor}`);
});

// Limitação: não podemos usar break ou continue
// Para interromper, precisamos usar exceções (não recomendado)

for...of com entries(), keys() e values()

const cores = ['vermelho', 'azul', 'verde'];

// values() - padrão do for...of
for (const cor of cores) {
  console.log(cor);
}

// keys() - obtém os índices
for (const indice of cores.keys()) {
  console.log(indice);
}

// entries() - obtém pares [índice, valor]
for (const [indice, cor] of cores.entries()) {
  console.log(`${indice}: ${cor}`);
}

Iteradores e o Protocolo Symbol.iterator

const arr = [10, 20, 30];
const iterador = arr[Symbol.iterator]();

console.log(iterador.next()); // { value: 10, done: false }
console.log(iterador.next()); // { value: 20, done: false }
console.log(iterador.next()); // { value: 30, done: false }
console.log(iterador.next()); // { value: undefined, done: true }

// Criando um iterador personalizado
function criarIterador(array) {
  let indice = 0;
  return {
    next: () => {
      if (indice < array.length) {
        return { value: array[indice++], done: false };
      }
      return { done: true };
    }
  };
}

5. Arrays no Node.js: Manipulação e Performance

Buffer vs. Array – Diferenças e Conversões

const { Buffer } = require('buffer');

// Buffer é mais eficiente para dados binários
const buffer = Buffer.from([0x48, 0x65, 0x6c, 0x6c, 0x6f]);
console.log(buffer.toString()); // 'Hello'

// Convertendo Buffer para Array
const arrayFromBuffer = Array.from(buffer);
console.log(arrayFromBuffer); // [72, 101, 108, 108, 111]

// Array para Buffer
const arrayNumeros = [72, 101, 108, 108, 111];
const bufferFromArray = Buffer.from(arrayNumeros);

Streams e Arrays Grandes – Processamento Sob Demanda

const readline = require('readline');
const fs = require('fs');

// Processando arquivo linha por linha (evita carregar tudo na memória)
async function processarArquivoGrande(caminho) {
  const rl = readline.createInterface({
    input: fs.createReadStream(caminho),
    crlfDelay: Infinity
  });

  const resultados = [];

  for await (const linha of rl) {
    // Processa cada linha individualmente
    const dados = JSON.parse(linha);
    if (dados.ativo) {
      resultados.push(dados);
    }
  }

  return resultados;
}

Uso de Array.from() com Objetos Iteráveis

// Convertendo Set em Array
const conjunto = new Set([1, 2, 3, 3, 4]);
const arrayUnico = Array.from(conjunto);
console.log(arrayUnico); // [1, 2, 3, 4]

// Convertendo Map em Array de pares
const mapa = new Map([['a', 1], ['b', 2]]);
const arrayPares = Array.from(mapa);
console.log(arrayPares); // [['a', 1], ['b', 2]]

// Usando o segundo argumento para transformar
const arrayTransformado = Array.from(mapa, ([chave, valor]) => ({
  chave,
  valor: valor * 2
}));

6. Arrays no React: Estado e Renderização

useState com Arrays – Imutabilidade e Spreads

import React, { useState } from 'react';

function ListaTarefas() {
  const [tarefas, setTarefas] = useState([]);

  // Adicionar tarefa (imutável)
  const adicionarTarefa = (novaTarefa) => {
    setTarefas(prev => [...prev, novaTarefa]);
  };

  // Remover tarefa (imutável)
  const removerTarefa = (id) => {
    setTarefas(prev => prev.filter(tarefa => tarefa.id !== id));
  };

  // Atualizar tarefa (imutável)
  const atualizarTarefa = (id, novosDados) => {
    setTarefas(prev => prev.map(tarefa => 
      tarefa.id === id ? { ...tarefa, ...novosDados } : tarefa
    ));
  };
}

Renderização de Listas com map() e key

function ListaUsuarios({ usuarios }) {
  return (
    <ul>
      {usuarios.map(usuario => (
        <li key={usuario.id}>
          {usuario.nome} - {usuario.email}
        </li>
      ))}
    </ul>
  );
}

// Nunca use o índice como key se a lista pode ser reordenada
// ❌ Ruim: {itens.map((item, index) => <Item key={index} />)}
// ✅ Bom: {itens.map(item => <Item key={item.id} />)}

Filtragem, Ordenação e Busca em Tempo Real com Hooks

import React, { useState, useMemo } from 'react';

function ListaProdutos({ produtos }) {
  const [busca, setBusca] = useState('');
  const [ordenacao, setOrdenacao] = useState('nome');

  const produtosFiltrados = useMemo(() => {
    return produtos
      .filter(produto => 
        produto.nome.toLowerCase().includes(busca.toLowerCase())
      )
      .sort((a, b) => {
        if (ordenacao === 'nome') {
          return a.nome.localeCompare(b.nome);
        }
        return a.preco - b.preco;
      });
  }, [produtos, busca, ordenacao]);

  return (
    <div>
      <input 
        type="text" 
        value={busca}
        onChange={e => setBusca(e.target.value)}
        placeholder="Buscar produtos..."
      />
      <select value={ordenacao} onChange={e => setOrdenacao(e.target.value)}>
        <option value="nome">Nome</option>
        <option value="preco">Preço</option>
      </select>

      {produtosFiltrados.map(produto => (
        <div key={produto.id}>
          {produto.nome} - R$ {produto.preco}
        </div>
      ))}
    </div>
  );
}

7. Boas Práticas e Padrões Comuns

Evitando Mutações Diretas

// ❌ Mutação direta (pode causar bugs em React)
const arr = [1, 2, 3];
arr.push(4);       // Muta o array original
arr.pop();         // Muta o array original
arr.splice(1, 1);  // Muta o array original

// ✅ Cópias imutáveis
const novoArr = [...arr, 4];           // Adicionar no final
const novoArr2 = [0, ...arr];          // Adicionar no início
const novoArr3 = arr.slice(0, 2);      // Remover elementos
const novoArr4 = arr.filter(x => x > 1); // Filtrar
const novoArr5 = arr.map(x => x * 2);  // Transformar

Desestruturação de Arrays

// Desestruturação básica
const [primeiro, segundo, ...resto] = [1, 2, 3, 4, 5];
console.log(primeiro); // 1
console.log(segundo);  // 2
console.log(resto);    // [3, 4, 5]

// Trocando valores sem variável temporária
let a = 1, b = 2;
[a, b] = [b, a];

// Desestruturação em parâmetros de função
function calcularMedia([primeiro, segundo, ...resto]) {
  const soma = primeiro + segundo + resto.reduce((acc, val) => acc + val, 0);
  return soma / (2 + resto.length);
}

// Retornando múltiplos valores
function dividir(dividendo, divisor) {
  return [Math.floor(dividendo / divisor), dividendo % divisor];
}
const [quociente, resto] = dividir(10, 3);

Encadeamento de Métodos

const usuarios = [
  { nome: 'Ana', idade: 25, ativo: true },
  { nome: 'João', idade: 17, ativo: true },
  { nome: 'Maria', idade: 30, ativo: false },
  { nome: 'Pedro', idade: 22, ativo: true }
];

// Encadeamento funcional - pipeline de transformação
const usuariosAtivosAdultos = usuarios
  .filter(usuario => usuario.ativo && usuario.idade >= 18)
  .map(usuario => ({
    ...usuario,
    faixaEtaria: usuario.idade >= 60 ? 'idoso' : 'adulto'
  }))
  .sort((a, b) => a.idade - b.idade);

console.log(usuariosAtivosAdultos);

Referências