Causas e Mitigações de Vazamentos de Memória em JavaScript

Em JavaScript, vazamentos de memória ocorrem quando objetos não utilizados não são coletados pelo garbage collector. Abaixo estão cenários comuns e soluções práticas.

Variáveis Globais Acidentais

Ao omitir declarações de variáveis, o JavaScript pode criar globais indesejadas.

// Variável global não declarada
function inicializar() {
  mensagem = "global acidental"; // Sem let/const/var
}

function configurar() {
  this.configGlobal = "configuração global"; // 'this' aponta para o objeto global em modo não estrito
}
configurar();

Solução: Utilize o modo estrito e declare variáveis explicitamente.

"use strict";

function inicializarSeguro() {
  let mensagemLocal = "variável local";
}

Temproizadores e Callbacks Esquecidos

Temporizadores e manipuladores de eventos podem manter referências a objetos, impedindo a coleta de lixo.

// Temporizador não limpo
const dadosCarregados = carregarDados();
setInterval(() => {
  const elemento = document.getElementById('info');
  if (elemento) {
    elemento.textContent = JSON.stringify(dadosCarregados);
  }
}, 1000);

// Ouvinte de evento não removido
const botao = document.getElementById('botao');
botao.addEventListener('clique', manipularClique);

Solução: Limpe temporizadores e remova ouvintes quando não necessários.

// Limpar temporizador
const idIntervalo = setInterval(funcaoCallback, 1000);
clearInterval(idIntervalo);

// Remover ouvinte
botao.removeEventListener('clique', manipularClique);

Referências ao DOM

Manter referências a elementos DOM removidos impede a liberação de memória.

const elementosCache = {
  botao: document.getElementById('botao'),
  imagem: document.getElementById('imagem')
};

function removerElemento() {
  document.body.removeChild(document.getElementById('botao'));
  // elementosCache.botao ainda aponta para o elemento removido
}

Solução: Defina referências como null após o uso.

function limparReferencias() {
  elementosCache.botao = null;
  elementosCache.imagem = null;
}

Closures

Closures podem capturar grandes volumes de dados, mantendo-os na memória.

function criarClosure() {
  const arrayGrande = new Array(100000).fill('x');
  return () => {
    console.log(arrayGrande.length);
    // arrayGrande permanece referenciado
  };
}

const funcaoClosure = criarClosure();

Solução: Libere closures não utilizadas.

function usarClosure() {
  let funcao = criarClosure();
  funcao(); // Usar
  funcao = null; // Liberar referência
}

Objetos de Cache Ilimitados

Caches sem limites crescem indefinidamente, consumindo memória.

const cache = {};
function armazenarCache(chave, valor) {
  cache[chave] = valor; // Sem controle de tamanho
}

Solução: Implemente caches com tamanho máximo.

class CacheLimitado {
  constructor(tamanhoMaximo = 50) {
    this.tamanhoMaximo = tamanhoMaximo;
    this.armazenamento = new Map();
  }
  adicionar(chave, valor) {
    if (this.armazenamento.size >= this.tamanhoMaximo) {
      const primeiraChave = this.armazenamento.keys().next().value;
      this.armazenamento.delete(primeiraChave);
    }
    this.armazenamento.set(chave, valor);
  }
}

Nós DOM Separados

Nós removidos da árvore DOM, mas mantidos em variáveis JavaScript, não são coletados.

let arvoreSeparada;
function gerarEstrutura() {
  const lista = document.createElement('ul');
  for (let i = 0; i < 5; i++) {
    const item = document.createElement('li');
    lista.appendChild(item);
  }
  arvoreSeparada = lista; // Referência mantida sem adicionar ao DOM
}
gerarEstrutura();

Solução: Limpe referências ou adicione ao DOM conforme necessário.

Ouvintes de Evento em Componentes

Em applicações SPA, ouvintes não removidos após a destruição de componentes causam leaks.

class Componente {
  constructor() {
    this.aoRedimensionar = this.aoRedimensionar.bind(this);
    window.addEventListener('resize', this.aoRedimensionar);
  }
  aoRedimensionar() {
    // Lógica
  }
  // Sem método de limpeza
}

Solução: Implemetne métodos de limpeza.

class ComponenteSeguro {
  constructor() {
    this.aoRedimensionar = this.aoRedimensionar.bind(this);
    window.addEventListener('resize', this.aoRedimensionar);
  }
  destruir() {
    window.removeEventListener('resize', this.aoRedimensionar);
  }
}

Práticas Recomendadas

  • Use o modo estrito para evitar variáveis globais acidentais.
  • Limpe temporizadores, ouvintes e closures após o uso.
  • Evite referências desnecessárias a elementos DOM.
  • Implemente limpeza em componentes destruídos.
  • Utilize WeakMap ou WeakSet para referências fracas.
// Exemplo com WeakMap
const mapaFraco = new WeakMap();
let elemento = document.getElementById('alvo');
mapaFraco.set(elemento, 'dados');

// Quando elemento é removido, a entrada no WeakMap é coletada automaticamente

Seguir essas práticas reduz significativamente vazamentos de memória em aplicações JavaScript.

Tags: javascript MemoryLeak DOM closures EventListeners

Publicado em 6-29 05:31