Estrutura De Dados Javascript - Array - Estrutura de dados com javascript - YouTube
Array - Estrutura de dados com javascript - YouTube

Entendendo estruturas de dados no JavaScript na prática

Quando eu comecei a trabalhar com JavaScript há alguns anos, achava que arrays e objetos eram tudo o que eu precisava. Isso mudou quando eu precisei processar milhares de registros em tempo real e o código simplesmente travava. A performance caiu de 2 segundos para 45 segundos sem nenhum motivo óbvio. Foi aí que eu entendi que precisar escolher a estrutura certa fazia toda diferença. O JavaScript oferece várias estruturas de dados nativas. Cada uma tem características específicas de performance e comportamento que afetam diretamente como seu código funciona. Vou explicar como eu aprendi a usar cada uma delas, os problemas que encontrei e quando evitar certas abordagens.

Por que estrutura de dados javascript importa no dia a dia

Eu já vi desenvolvedores usarem objetos como dicionários quando deveria usar Map, ou arrays quando Set seria mais adequado. Isso não é um erro grave em projetos pequenos, mas em aplicações que processam milhões de dados, a diferença pode ser de minutos para segundos. O problema é que muitas pessoas copiam código de tutoriais sem entender as implicações de performance. Um objeto simples parece rápido porque você acessa propriedades com notação de ponto, mas há custos ocultos que aparecem apenas em escala.

Arrays: o que todo mundo usa, mas poucos dominam

Arrays são a estrutura mais comum no JavaScript. Você pode adicionar, remover, acessar elementos e fazer mapas com facilidade. Mas existem armadilhas importantes que eu descobri na prática. Quando eu precisava verificar se um elemento existia em um array grande, eu costumava usar o método includes. Para arrays com menos de 100 elementos, isso funciona bem. Mas quando o array crescia para milhares de itens, a operação ficava absurdamente lenta porque o includes verifica cada elemento sequencialmente. Eu resolvi isso convertendo o array para um Set antes de fazer as verificações. A diferença foi de 3 segundos para 2 milissegundos.

Outro problema que eu enfrentei foi com o método splice. Quando eu removia elementos do meio de um array grande, todos os elementos após o ponto de remoção precisavam ser realocados na memória. Em arrays com milhares de elementos, isso acontecia repetidamente e causava problemas sérios de performance. A solução foi criar uma nova estrutura de dados, preferencialmente um Map, para manter os dados e apenas sincronizar com o array quando necessário. Eu também aprendi que o forEach não pode ser interrompido com break. Se você precisa parar de processar quando encontra uma condição específica, tem que usar um loop for tradicional ou o find. Isso parece óbvio, mas eu vi muitos códigos usando forEach quando um for seria mais eficiente.

Objetos: a estrutura mais mal compreendida

Objetos são usados para quase tudo no JavaScript. Você pode armazenar pares chave-valor, criar instâncias, herdar propriedades. Mas existem limitações importantes que as pessoas ignoram. Uma coisa que eu descobri depois de dor de cabeça é que objetos convertem automaticamente todas as chaves para string. Isso significa que se você usar um número ou objeto como chave, ele será convertido para string. Eu já perdi horas debugando código porque um objeto estava sendo usado como chave e o JavaScript estava convertendo para "[object Object]". A solução foi usar Map, que preserva o tipo original das chaves.

Outro problema é com a herança de propriedades. Quando você itera sobre um objeto com for...in, ele inclui propriedades herdadas da prototype chain. Isso pode causar bugs sutis. Eu resolvi isso usando Object.keys() para iterar apenas sobre propriedades próprias, ou Object.entries() quando preciso também dos valores. Existe também o problema de performance quando você remove propriedades de objetos grandes. O V8 engine do Chrome tem que reorganizar internamente o objeto, o que pode causar pauses significativas em Objetos com milhares de propriedades. Eu descobri isso quando minha aplicação começava a engasgar após operações de limpeza frequentes.

Map e Set: as estruturas que mudaram meu código

Map e Set foram adicionados ao JavaScript ES6 e resolvem muitos problemas que eu enfrentava com objetos e arrays. Eu awalnya não via a necessidade, mas depois de experimentar, nunca mais voltei. Map permite usar qualquer tipo como chave, não apenas strings. Isso resolveu um problema específico que eu tinha onde precisava usar objetos como chaves para lookup. Com Map, eu podia usar o objeto diretamente como chave e ele mantinha a referência correta. Antes, eu tinha que serializar o objeto para string, o que era propenso a erros e causava problemas quando o objeto era modificado.

Set é útil quando você precisa de uma coleção de valores únicos. Eu usava arrays para isso, mas tinha que verificar manualmente se o elemento já existia antes de adicionar. Com Set, a verificação de duplicação é automática e mais rápida. Quando eu precisava verificar se um elemento existia em uma coleção grande, a diferença de performance foi impressionante. Um problema que eu encontrei com Map é que ele não preserva a ordem de inserção de forma consistente em todos os navegadores antigos. Se você precisa de ordem específica, tem que usar um array separado ou garantir que está usando um ambiente moderno.

👉 Clique no botão abaixo para saber mais sobre o assunto!

WeakMap e WeakSet: quando usar e quando evitar

WeakMap e WeakSet são estruturas especiais que permitem que os objetos sejam coletados pelo garbage collector. Isso é útil quando você precisa anexar dados a objetos sem prevenir que eles sejam coletados. Eu usei WeakMap para armazenar metadados sobre elementos DOM sem vazar memória. Quando o elemento era removido da página, o WeakMap automaticamente removia a referência, permitindo que o garbage collector limpasse a memória. Isso resolveu um problema de memory leak que eu tinha em uma aplicação SPA onde eu armazenava dados relacionados a componentes.

Porém, WeakMap tem limitações importantes. Você não pode iterar sobre as chaves ou valores de um WeakMap. Se você precisa saber quantos elementos existem ou listar todos os dados, tem que usar Map em vez disso. Também não posso usar tipos primitivos como chaves, apenas objetos. WeakSet funciona de forma similar, mas só permite objetos como membros. Eu tentei usar WeakSet para rastrear elementos DOM que já foram processados, mas percebi que a falta de capacidade de iteração tornava difícil depurar problemas. Preferi usar Set quando precisava de visibilidade dos dados.

Performance e casos extremos que eu encontrei

Existem situações onde a escolha da estrutura de dados faz diferença crítica de performance. Eu tive um caso onde eu precisava fazer lookup frequente por IDs em uma coleção grande de objetos. No início, eu usei um array e filtrava com find. Para 100 elementos, funcionava bem. Quando a coleção cresceu para 10.000 elementos, o tempo de resposta passou de 50ms para 2 segundos. Eu converti para um objeto onde a chave era o ID, e o tempo caiu para 1ms. A lição é que lookup em objetos é O(1), enquanto lookup em arrays é O(n).

Outro problema que eu encontrei foi com a remoção de elementos de arrays em loops. Quando eu removia elementos enquanto iterava, o índice dos elementos subsequentes mudava, causando comportamentos inesperados. A solução foi iterar de trás para frente ou criar um novo array com os elementos que você quer manter. Também descobri que a conversão entre tipos de estruturas tem custos. Converter um array grande para Set e depois de volta para array pode levar mais tempo do que simplesmente usar Set desde o início. Eu otimizei meu código removendo conversões desnecessárias e vi melhora significativa.

Pitfalls comuns e como evitar

Um erro comum é confundir igualdade de valor com igualdade de referência em objetos. Dois objetos com as mesmas propriedades não são iguais no JavaScript. Eu já passei por situations onde eu comparava objetos com == ou === e sempre recebia false, mesmo quando o conteúdo parecia idêntico. A solução é usar JSON.stringify() para comparação profunda, mas isso tem limitações com funções e undefined. Uma alternativa melhor é usar bibliotecas especializadas como lodash isEqual, ou implementar uma função de comparação personalizada baseada no caso de uso.

Outro erro é modificar objetos durante iteração. Quando você adiciona ou remove propriedades de um objeto enquanto itera com for...in, o comportamento é imprevisível. Eu resolvi isso criando uma lista de mudanças e aplicando após a iteração, ou usando Object.entries() que cria uma snapshot dos dados no momento da iteração. Arrays também têm problemas quando você usa métodos como splice dentro de loops. Eu aprendi que é mais seguro usar filter para criar um novo array com os elementos desejados, em vez de modificar o array existente. A diferença de performance é mínima para arrays pequenos, mas para arrays grandes, filter é mais previsível e menos propenso a bugs.

Estrutura de dados javascript para diferentes cenários

Escolher a estrutura certa depende do caso de uso. Para lookup frequente por chave, Map é melhor que objeto quando você precisa preservar tipos de chave. Para coleções de valores únicos, Set é mais eficiente que array. Para dados temporários anexados a objetos, WeakMap evita vazamentos de memória. Eu recomendo começar com a estrutura mais simples que resolve o problema. Se performance se tornar um problema, faça profiling para identificar gargalos antes de otimizar prematuramente. Ferramentas como Chrome DevTools Performance tab e linter como ESLint com regras de performance podem ajudar a identificar problemas cedo.

Não existe solução perfeita para todos os casos. Cada estrutura tem trade-offs entre performance, memória e facilidade de uso. O importante é entender essas diferenças e escolher conscientemente baseado nos requisitos do seu projeto.