Executando verificação de segurança...
2
thdr
4 min de leitura ·

Para onde vão nossas variáveis? Um guia breve de Call Stack e Memory Heap no Javascript

Você já deve ter construído inúmeros códigos em Javascript, mas você já parou para se perguntar como algumas coisas funcionam por debaixo dos panos?

Hoje quero apresentar para vocês dois termos que eu não conhecia e achei interessante de conhecer, Call Stack e Memory Heap.

Modelo de memória do Javascript

Primeiro, precisamos entender o que acontece quando declaramos uma variável e inicializamos ela. No exemplo abaixo, declarei a variável x com o valor de 1 e a variável y equivalente a variável x, e depois mudei o valor de x.

let x = 1
let y = x

x = x + 1;

console.log(y) // Resultado?

O que você acha que vai acontecer quando imprimirmos o valor de y?

Se você acha que o valor de y vai ser 2, você está errado, mas deixa eu te explicar o porquê.

  1. Quando x foi criado, o Javascript criou um identificador único e alocou ele em um endereço de memória (exemplo: M001) e com isso salvou também o valor da variável no endereço de memória.
  2. Quando eu disse que let y = x eu quis dizer que y é equivalente ao endereço de memória M001 e não igual ao valor de x
  3. Quando eu alterei o valor de x, o Javascript alocou um novo endereço de memória e salvou o valor 2 no endereço M002 e isso aconteceu por que tipos primitivos de dados (string, number, boolean, undefined e symbol) são imutáveis

E qual o papel do Call Stack?

O Call stack funciona como uma pilha (FILO - First In, Last Out). Ele organiza as chamadas das funções, adicionando-as e a removendo-as após a execução do código. Além disso, ele é responsável por armazenar os tipos primitivos.

E é aqui, no call stack, que ocorre um fenômeno que você já deve ter ouvido falar, o Stack Overflow, que nada mais é o que ocorre quando funções são empilhadas indefinidamente e a pilha excede a quantidade máxima de memória.

Um exemplo rápido de como podemos causar um stack overflow é utilizando recursão:

function recursion() {
	recursion();
}

recursion();

Qual a função do Memory Heap, então?

Diferente do Call Stack, o Memory Heap é onde os dados não primitivos (arrays, objetos e funções) são armazenados. Ele permite que os dados cresçam dinamicamente e sejam organizado de maneira não sequencial.

O memory heap funciona da seguinte forma. Olhe este exemplo:

const arr = [];
arr.push(1);

Nesse caso, quando declaramos a variável arr, o Call Stack vai salvar dentro dela uma referência, apontando para o Memory Heap, onde os valores reais são manipulados. Nesse caso, o push altera o conteúdo do Heap, mas não o endereço de referência no Call Stack.

const e dados não primitivos

Quando declaramos uma variável com const, não podemos reatribuir seu valor. Mas, no caso de arrays e objetos, é possível modificar os itens internamente. Isso acontece porque o identificador no call stack permanece o mesmo, enquanto os valores são gerenciados no Heap.

Resumindo, quando realizamos um push em algum array, ele não altera o endereço de memória, ele altera o valor no heap, e a mesma coisa vale para objetos.

Tabela de referência para você entender melhor:

VariávelCall StackHeap
EndereçoValorEndereçoValor
xCS0012
yCS0021
arrCS003 (aponta para H001)H001 (aponta para ->)[]

Mas existem casos em que os tipos primitivos podem "ir" para o Heap?

Sim, existem casos em que os tipos primitivos podem "ir" para o Heap, isso acontece indiretamente quando você usa um wrapper.

Por exemplo, se eu declarar uma variável como new String("Text") ao invés de "text", o valor é tratado como um objeto e armazenado no Heap

Garbage Collection e Memory Leaks

O Javascript possui um gerenciamento automático de memória, ou seja, ele limpa endereços não referenciados (ou seja, que não são mais utilizados) automaticamente (isso é o "garbage collection"). Ele usa o algoritmo Mark and Sweep, que identifica e remove variáveis inacessíveis.

Mas ainda sim, Memory Leaks (que é quando excedemos a memória disponível) podem ocorrer. Veja os casos mais comuns de Memory Leaks:

Variáveis globais
Variáveis globais são difíceis de coletar, pois estão sempre acessíveis:

const x = 1
const y = 2;
const z = 3

Event Listeners
Event Listeners não removidos podem continuar ativos, gerando os leaks:

const button = document.getElementById('myButton'); 

// Adiciona um event listener ao botão
button.addEventListener('click', () => { console.log('Button clicked!'); }); 

// Problema: o listener não será removido mesmo que o botão seja excluído da DOM 
document.body.removeChild(button);

setInterval
Funções dentro de um setInterval nunca são coletadas, a menos que o intervalo seja limpo:

setInterval(() => {
	let x = 1;
})

Conclusão

Compreender o modelo de memória do JavaScript é essencial para escrever códigos mais performáticos e evitar problemas como memory leaks.

Lembre-se:

  1. Tipos primitivos são imutáveis e armazenados no Call Stack como valores.
  2. Tipos não primitivos residem no Memory Heap e são armazenados como referência.
  3. Exceder o limite da pilha causa um Stack Overflow
  4. Exceder o limite da memória disponível causa Memory leaks.

E caso queira aprender mais sobre variáveis de valores e referência, leia este artigo que escrevi, explicando o porque não conseguimos comparar arrays e objetos com ===.

Achei interessante aprender um pouco sobre isso e espero que você aproveite também, até breve!

Carregando publicação patrocinada...