Em JavaScript, variáveis são utilizadas para armazenar dados que podem ser manipulados durante a execução do programa. Desde o ES6 (ECMAScript 2015), temos três formas de declarar variáveis: var, let e const. Cada uma possui características distintas de escopo, hoisting e possibilidade de reatribuição. Compreender essas diferenças é fundamental para evitar bugs e escrever código claro e previsível.

Nesta aula, vamos explorar cada um desses tipos de declaração, analisar seu comportamento em diferentes contextos e definir as melhores práticas para seu uso no dia a dia.

Declaração e atribuição

A declaração de uma variável cria um identificador no escopo atual. A atribuição armazena um valor nesse identificador. Com var, let e const, a sintaxe é semelhante, mas há diferenças importantes.

var permite redeclarar a mesma variável no mesmo escopo sem erro, o que pode causar confusão. Já let e const não permitem redeclaração no mesmo escopo. const exige que a variável seja inicializada na declaração e não pode ser reatribuída. Exemplos:

var nome = "João";
var nome = "Maria"; // permitido, sobrescreve

let idade = 30;
// let idade = 25; // Erro: Identificador 'idade' já foi declarado

const PI = 3.1415;
// PI = 3; // Erro: Atribuição a variável constante.
// const X; // Erro: Falta inicializador na declaração const

É importante notar que const não torna o valor imutável; apenas impede a reatribuição do identificador. Se o valor for um objeto, suas propriedades ainda podem ser alteradas.

Escopo de função vs bloco

O escopo determina onde uma variável pode ser acessada. var tem escopo de função (function scope), ou seja, é visível em toda a função onde foi declarada, independentemente de blocos internos. Já let e const têm escopo de bloco (block scope), limitado ao bloco { } onde foram declarados.

Exemplo com var:

function exemploVar() {
  if (true) {
    var x = 10;
  }
  console.log(x); // 10, acessível fora do bloco
}

Exemplo com let:

function exemploLet() {
  if (true) {
    let y = 20;
  }
  console.log(y); // ReferenceError: y is not defined
}

Essa diferença torna let e const mais seguros, pois evitam vazamento de variáveis para escopos maiores do que o necessário.

Hoisting

Hoisting é o comportamento do JavaScript de mover declarações para o topo do escopo antes da execução. Com var, a declaração é içada (hoisted) e inicializada com undefined, permitindo acesso antes da linha de declaração. Já let e const também são içadas, mas não são inicializadas, permanecendo na "zona morta temporal" (temporal dead zone) até a declaração.

Exemplo com var:

console.log(a); // undefined (não causa erro)
var a = 5;

Exemplo com let:

console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 10;

O mesmo vale para const. Esse comportamento incentiva boas práticas, pois acessar uma variável antes de sua declaração é quase sempre um erro lógico.

Quando usar cada uma

Atualmente, a recomendação geral é evitar var em novos códigos, devido ao seu escopo de função e comportamento de hoisting que podem levar a bugs inesperados. Prefira const sempre que o valor não precisar ser reatribuído, e use let apenas quando a reatribuição for necessária (por exemplo, em contadores de laços).

Boas práticas:

  • Use const por padrão.
  • Use let quando precisar reatribuir.
  • Evite var em código moderno.
  • Declare variáveis no topo do escopo de bloco para evitar confusão.

Exemplo em um laço for:

// Com let, cada iteração tem seu próprio i
for (let i = 0; i < 5; i++) {
  setTimeout(() => console.log(i), 100); // 0,1,2,3,4
}

// Com var, todas as iterações compartilham o mesmo i
for (var j = 0; j < 5; j++) {
  setTimeout(() => console.log(j), 100); // 5,5,5,5,5
}

Isso mostra como let resolve um problema clássico de closures em laços.

Referências

Exercícios

  1. Qual será a saída do código abaixo e por quê?

    console.log(x);
    var x = 5;
    console.log(x);
    

    ✓ Resposta: A saída será undefined e depois 5. Isso ocorre porque a declaração var x é hoisted para o topo, mas a atribuição permanece no lugar. Então, o primeiro console.log encontra x como undefined.
  2. O código a seguir funciona? Explique.

    if (true) {
      let a = 1;
    }
    console.log(a);
    

    ✓ Resposta: Não, gera um ReferenceError porque let tem escopo de bloco. A variável a só existe dentro do bloco if, e o console.log está fora dele.
  3. Converta o código abaixo para usar let ou const de forma adequada, eliminando o uso de var:

    var nome = "Ana";
    var idade = 25;
    idade = 26;
    var PI = 3.14;
    

    ✓ Resposta:
    const nome = "Ana";
    let idade = 25;
    idade = 26;
    const PI = 3.14;
    
  4. Qual a diferença entre const e imutabilidade? Dê um exemplo.

    ✓ Resposta: const impede a reatribuição do identificador, mas o valor em si pode ser mutável. Exemplo: const obj = { a: 1 }; obj.a = 2; // permitido. Se quiser imutabilidade, use Object.freeze ou bibliotecas como Immutable.js.
  5. Explique o que é a "zona morta temporal" (temporal dead zone) com um exemplo.

    ✓ Resposta: A zona morta temporal é o período entre a entrada no escopo e a declaração real da variável com let ou const, durante o qual a variável não pode ser acessada. Exemplo:
    console.log(x); // ReferenceError
    let x = 10;