var, let e const: diferenças e quando usar cada um

1. Introdução à declaração de variáveis em JavaScript

Desde o início do JavaScript, var era a única forma de declarar variáveis. Com a chegada do ECMAScript 2015 (ES6), let e const foram introduzidos para resolver problemas históricos de escopo e previsibilidade. A principal motivação foi oferecer controle mais granular sobre como e onde as variáveis existem no código, eliminando comportamentos confusos como hoisting sem inicialização e escopo de função que vazava para fora de blocos.

As três formas diferem em três aspectos fundamentais: escopo, hoisting e reatribuição. Entender essas diferenças é essencial para escrever código previsível em Node.js e React.

2. Escopo de variáveis: global, função e bloco

var possui escopo de função (function scope). Isso significa que uma variável declarada com var dentro de uma função é acessível em toda a função, independentemente de onde foi declarada. Já let e const possuem escopo de bloco (block scope), ficando restritas ao bloco {} onde foram declaradas.

function exemploEscopo() {
  if (true) {
    var x = 10;   // escopo de função
    let y = 20;   // escopo de bloco
    const z = 30; // escopo de bloco
  }
  console.log(x); // 10 (acessível)
  console.log(y); // ReferenceError: y is not defined
  console.log(z); // ReferenceError: z is not defined
}

Em loops, essa diferença é crítica:

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100); // 3, 3, 3
}

for (let j = 0; j < 3; j++) {
  setTimeout(() => console.log(j), 100); // 0, 1, 2
}

3. Hoisting e a zona temporal morta (Temporal Dead Zone)

Hoisting é o comportamento do JavaScript de mover as declarações para o topo do escopo antes da execução. var é içada e inicializada como undefined:

console.log(a); // undefined
var a = 5;

let e const também são içadas, mas não são inicializadas. Elas entram em uma zona temporal morta (TDZ) desde o início do escopo até a declaração:

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

console.log(c); // ReferenceError: Cannot access 'c' before initialization
const c = 15;

A TDZ previne acessos acidentais a variáveis antes de sua inicialização, tornando o código mais seguro.

4. Reatribuição e redeclaração

var permite tanto reatribuição quanto redeclaração no mesmo escopo:

var nome = "João";
var nome = "Maria"; // redeclaração permitida
nome = "Pedro";     // reatribuição permitida

let permite reatribuição, mas proíbe redeclaração:

let idade = 25;
idade = 26;         // reatribuição permitida
let idade = 30;     // SyntaxError: Identifier 'idade' has already been declared

const proíbe ambas:

const PI = 3.14159;
PI = 3.14;          // TypeError: Assignment to constant variable
const PI = 3;       // SyntaxError: Identifier 'PI' has already been declared

5. const e mutabilidade de objetos e arrays

É crucial entender que const impede a reatribuição da referência, mas não torna o valor imutável. Objetos e arrays declarados com const podem ter seu conteúdo modificado:

const pessoa = { nome: "Ana", idade: 30 };
pessoa.idade = 31;          // permitido
pessoa.cidade = "São Paulo"; // permitido
// pessoa = {};             // TypeError: Assignment to constant variable

const numeros = [1, 2, 3];
numeros.push(4);            // permitido
numeros[0] = 10;            // permitido
// numeros = [];            // TypeError: Assignment to constant variable

Para imutabilidade profunda, use Object.freeze():

const config = Object.freeze({
  api: "https://api.exemplo.com",
  timeout: 5000
});
config.timeout = 3000; // não gera erro em modo não estrito, mas não altera

6. Quando usar cada um: guia prático para Node.js e React

const como padrão — Use const para tudo que não precisa ser reatribuído. Em React, componentes, funções, hooks e imports devem usar const:

// React component
const MeuComponente = () => {
  const [contador, setContador] = useState(0);
  const handleClick = () => setContador(contador + 1);
  return <button onClick={handleClick}>{contador}</button>;
};

let para variáveis que precisam ser reatribuídas — Contadores, acumuladores, flags:

// Node.js example
let total = 0;
for (let i = 0; i < dados.length; i++) {
  total += dados[i].valor;
}

var apenas em código legado — Em projetos modernos, evite var. Use-o somente quando precisar do escopo de função explicitamente ou ao manter código antigo.

Regras de ouro:
- Projetos Node.js e React modernos: const como padrão, let quando necessário, var nunca (a menos que estrito legado)
- Prefira const em imports/exports de módulos
- Configure ESLint com no-var e prefer-const

7. Armadilhas comuns e boas práticas

Erro clássico com var em closures:

// Problema
var botoes = document.querySelectorAll('button');
for (var i = 0; i < botoes.length; i++) {
  botoes[i].onclick = function() {
    console.log(i); // sempre exibe o último valor
  };
}

// Solução com let
for (let i = 0; i < botoes.length; i++) {
  botoes[i].onclick = function() {
    console.log(i); // valor correto para cada botão
  };
}

Strict mode afeta var? Não diretamente, mas redeclarações acidentais em strict mode geram erros. let e const já são strict por padrão.

ESLint recomendado:

// .eslintrc.json
{
  "rules": {
    "no-var": "error",
    "prefer-const": "error"
  }
}

8. Exemplos comparativos em Node.js e React

Exemplo Node.js: servidor HTTP

// Antes (código legado)
var http = require('http');
var porta = 3000;
var servidor = http.createServer(function(req, res) {
  res.end('Olá');
});

// Depois (moderno)
const http = require('http');
const PORTA = 3000;
const servidor = http.createServer((req, res) => {
  res.end('Olá');
});
servidor.listen(PORTA);

Exemplo React: gerenciamento de estado

// Componente funcional com hooks
const Contador = () => {
  const [count, setCount] = useState(0);
  const [historico, setHistorico] = useState([]);

  const incrementar = () => {
    setCount(prev => {
      const novoValor = prev + 1;
      setHistorico([...historico, novoValor]);
      return novoValor;
    });
  };

  return (
    <div>
      <p>Contagem: {count}</p>
      <button onClick={incrementar}>+</button>
    </div>
  );
};

Testando escopo no Node.js vs navegador — O comportamento é idêntico em ambos, mas o Node.js tem módulos (CommonJS/ESM) que afetam o escopo global. Em módulos ES6, var no topo não cria propriedade global, enquanto no navegador cria window.varName.

Referências

  • MDN Web Docs: var — Documentação oficial sobre a declaração var, incluindo escopo de função e hoisting.
  • MDN Web Docs: let — Documentação completa sobre let, escopo de bloco e zona temporal morta.
  • MDN Web Docs: const — Guia oficial sobre const, imutabilidade de referência e mutabilidade de objetos.
  • JavaScript.info: Variables — Tutorial interativo cobrindo var, let e const com exemplos práticos e exercícios.
  • W3Schools: JavaScript Let — Explicação didática das diferenças entre var, let e const com exemplos simples.
  • ESLint: no-var rule — Regra do ESLint que desencoraja o uso de var, com exemplos de código correto e incorreto.
  • freeCodeCamp: var vs let vs const — Artigo detalhado comparando as três formas de declaração com exemplos em Node.js e React.