var: O Velho Paradigma (e Por Que Evitá-lo)
A palavra-chave var foi a forma original de declarar variáveis em JavaScript. Ela possui escopo de função (function scope) e não de bloco, o que cria comportamentos inesperados. Além disso, var permite redeclaração na mesma função, tornando o código frágil em projetos grandes.
function exemplo() {
if (true) {
var x = 10;
}
console.log(x); // 10 — x é acessível fora do if!
}
function redeclaracao() {
var contador = 0;
var contador = 1; // Sem erro, sobrescreve
console.log(contador); // 1
}
Em produção, você encontrará var apenas em código legado. Frameworks modernos e linters (como ESLint) desestimulam seu uso. A razão é simples: comportamentos erráticos levam a bugs difíceis de rastrear em aplicações complexas.
let e const: O Padrão Moderno
let e const introduzem block scope — a variável existe apenas dentro do bloco {} onde foi declarada. Essa é a mudança paradigmática que tornou JavaScript mais previsível. A diferença entre elas é crucial: let permite reatribuição, const não.
function escopo() {
if (true) {
let y = 20;
const z = 30;
}
console.log(y); // ReferenceError: y is not defined
console.log(z); // ReferenceError: z is not defined
}
// Exemplo prático em produção
const usuario = { nome: "Ana" };
usuario.nome = "Bruno"; // Funciona — mudamos propriedade
usuario = {}; // TypeError: Assignment to constant variable
let contador = 0;
contador = 1; // Funciona — let permite reatribuição
Quando Usar let vs const
Use const como padrão. Ela deixa clara sua intenção de não reatribuir e força pensamento sobre estrutura de dados. Use let apenas quando a reatribuição for necessária. Essa convenção reduz bugs em equipes grandes. Em um projeto real, você espera ver 80% const e 20% let.
// Bom
const API_URL = "https://api.exemplo.com";
const dados = fetch(API_URL);
let tentativas = 0;
// Evitar
let nome = "João"; // Deveria ser const se não reatribuir
const config = {}; // const não garante imutabilidade interna
Escopo em Profundidade: Closure e Contexto
Escopo em JavaScript é hierárquico. Uma função interna acessa variáveis da função externa — isso é closure. Isso é poderoso, mas também é fonte de vazamento de memória se mal usado. Em produção, você precisa entender como o garbage collector interage com closures.
// Padrão comum: factory functions
function criarContador(inicio = 0) {
let valor = inicio; // Privada, não acessível externamente
return {
incrementar: () => ++valor,
obterValor: () => valor,
resetar: () => { valor = inicio; }
};
}
const contador1 = criarContador(10);
console.log(contador1.incrementar()); // 11
console.log(contador1.obterValor()); // 11
// valor não é acessível: contador1.valor == undefined
// Problema: vazamento de closure
function cacheComVazamento() {
const cache = [];
return function adicionarAoCache(item) {
cache.push(item); // cache cresce indefinidamente
};
}
const adicionar = cacheComVazamento();
// Se chamar 1 milhão de vezes, cache usa memória desnecessária
Escopo Global e Efeitos Colaterais
Variáveis no escopo global (declaradas fora de funções) são acessíveis em qualquer lugar. Em produção, evite isso — causa acoplamento e torna testes impossíveis. Use módulos (ES6 modules) para encapsular.
// Evitar em produção
var estadoGlobal = { usuario: null }; // Acoplamento total
// Padrão correto com módulos
// arquivo: usuario.js
let estadoLocal = { usuario: null };
export function setUsuario(u) {
estadoLocal.usuario = u;
}
export function getUsuario() {
return estadoLocal.usuario;
}
// arquivo: main.js
import { setUsuario } from './usuario.js';
setUsuario({ id: 1, nome: "Carlos" });
Boas Práticas em Produção
Em código profissional, você verá padrões estabelecidos. Primeiro: sempre declare variáveis no menor escopo possível. Segundo: use nomes descritivos — const userData é melhor que const d. Terceiro: combine com destructuring para código mais limpo.
// Destructuring reduz escopo desnecessário
const { nome, email } = usuario;
const [primeiro, segundo] = array;
// Cuidado com this em closures
const obj = {
valor: 100,
incrementar: function() {
const adicionar = (x) => this.valor + x; // Arrow function mantém this
return adicionar(5); // 105
}
};
// Object.freeze para pseudo-constância
const config = Object.freeze({
timeout: 5000,
retries: 3
});
// config.timeout = 10000; // Falha silenciosamente em non-strict mode
Linters como ESLint forçam essas práticas. Configure no seu projeto para alertar sobre var, variáveis não utilizadas e escopos confusos. Isso economiza horas de debugging.
Conclusão
Domine três pontos essenciais: use const por padrão, prefira block scope natural de let/const em vez de function scope de var, e compreenda closures para evitar vazamento de memória. JavaScript moderno é mais seguro quando você respeita escopo. Em código de produção, essas práticas não são opcionais — são o diferencial entre sistemas mantíveis e pesadelos de manutenção.