Escopo: global, local, de bloco e closures
1. Introdução ao conceito de escopo em JavaScript
Escopo define onde variáveis e funções são acessíveis dentro do código. É um dos conceitos mais fundamentais do JavaScript, pois determina como o motor da linguagem resolve nomes de identificadores. Sem entender escopo, você inevitavelmente encontrará bugs misteriosos.
JavaScript utiliza escopo léxico (ou estático), onde o escopo de uma variável é determinado pela posição onde ela é declarada no código-fonte, durante a fase de compilação. Diferentemente do escopo dinâmico (usado em linguagens como Bash), o escopo léxico não depende da pilha de chamadas em tempo de execução.
A cadeia de escopos (scope chain) é o mecanismo que o motor do JavaScript usa para resolver variáveis: quando uma variável é referenciada, o motor busca primeiro no escopo atual, depois no escopo pai, e assim sucessivamente até chegar ao escopo global. Se não encontrar, lança um ReferenceError.
const global = 'estou no escopo global';
function externa() {
const localExterna = 'escopo da função externa';
function interna() {
const localInterna = 'escopo da função interna';
console.log(global); // acessível via cadeia de escopos
console.log(localExterna); // acessível via cadeia de escopos
console.log(localInterna); // acessível (escopo atual)
}
interna();
// console.log(localInterna); // ReferenceError: não acessível aqui
}
externa();
2. Escopo global: características e riscos
O escopo global é o escopo padrão quando você declara variáveis fora de qualquer função ou bloco. Em navegadores, o objeto global é window; no Node.js, é global.
// Escopo global
var variavelGlobalVar = 'acessível em todo lugar';
let variavelGlobalLet = 'também global, mas não no objeto global';
const constanteGlobal = 'imutável e global';
function exemplo() {
console.log(variavelGlobalVar); // funciona
console.log(variavelGlobalLet); // funciona
}
Problemas comuns:
- Poluição global: Múltiplos scripts podem sobrescrever variáveis globais acidentalmente.
- Colisão de nomes: Duas bibliotecas diferentes usando o mesmo nome global.
- Dificuldade de manutenção: Código difícil de testar e depurar.
Boas práticas:
- Em Node.js, cada módulo tem seu próprio escopo — variáveis declaradas no topo de um módulo não vazam para o global.
- Em React, evite variáveis globais dentro de componentes; prefira estado ou contexto.
// Em Node.js, isso não polui o global
const config = { api: 'https://api.exemplo.com' };
// config não está em global.config
3. Escopo local (de função): variáveis e parâmetros
Cada função em JavaScript cria seu próprio escopo. Variáveis declaradas dentro de uma função (com var, let ou const) e seus parâmetros ficam acessíveis apenas dentro dela.
function calcularPreco(precoBase, imposto) {
// precoBase e imposto são variáveis locais (parâmetros)
var precoFinal = precoBase * (1 + imposto); // var tem escopo de função
if (precoFinal > 100) {
var desconto = 0.1; // var "vaza" para o escopo da função
let descontoLet = 0.15; // let fica restrito ao bloco
console.log(descontoLet); // acessível aqui
}
console.log(desconto); // 0.1 (var vazou do if)
// console.log(descontoLet); // ReferenceError
return precoFinal;
}
Hoisting: Declarações com var são içadas (hoisted) para o topo do escopo da função, mas sem inicialização. let e const também sofrem hoisting, mas ficam na "zona morta temporal" (Temporal Dead Zone) até a declaração.
function exemploHoisting() {
console.log(x); // undefined (hoisting de var)
// console.log(y); // ReferenceError: TDZ
var x = 10;
let y = 20;
}
4. Escopo de bloco: let, const e a diferença do var
Blocos {} (usados em if, for, while) criam escopo apenas para let e const. var ignora blocos e respeita apenas escopo de função.
if (true) {
var escopoVar = 'vaza do bloco';
let escopoLet = 'preso no bloco';
const escopoConst = 'também preso no bloco';
}
console.log(escopoVar); // 'vaza do bloco'
// console.log(escopoLet); // ReferenceError
// console.log(escopoConst); // ReferenceError
Problema clássico de closures com var em laços:
// Problema com var
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 3, 3, 3
}
// Solução com let (escopo de bloco)
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log(j), 100); // 0, 1, 2
}
Com var, todas as funções callback compartilham a mesma variável i (que já vale 3 quando os timeouts executam). Com let, cada iteração cria um novo escopo de bloco, capturando o valor correto.
5. Closures: capturando escopos de forma permanente
Uma closure é uma função que "lembra" do escopo onde foi criada, mesmo após esse escopo ter sido executado. É um dos recursos mais poderosos do JavaScript.
function criarContador() {
let contador = 0; // variável privada
return function() {
contador++;
return contador;
};
}
const meuContador = criarContador();
console.log(meuContador()); // 1
console.log(meuContador()); // 2
console.log(meuContador()); // 3
A função retornada mantém uma referência ao escopo de criarContador, mantendo contador vivo na memória.
Closures em React:
function ContadorComponente({ incremento }) {
const [contador, setContador] = useState(0);
// Closure captura incremento e contador
const handleClick = () => {
setContador(prev => prev + incremento);
};
return <button onClick={handleClick}>Clique</button>;
}
6. Closures na prática: módulos e encapsulamento
Padrão IIFE (Immediately Invoked Function Expression) para criar escopo privado:
const modulo = (function() {
let variavelPrivada = 0;
function metodoPrivado() {
console.log('acessível apenas internamente');
}
return {
incrementar: function() {
variavelPrivada++;
metodoPrivado();
},
obterValor: function() {
return variavelPrivada;
}
};
})();
modulo.incrementar();
console.log(modulo.obterValor()); // 1
// console.log(modulo.variavelPrivada); // undefined
Módulos ES6 gerenciam escopo automaticamente:
// arquivo: contador.js
let contador = 0; // privado ao módulo
export function incrementar() {
contador++;
}
export function obter() {
return contador;
}
CommonJS em Node.js:
// arquivo: logger.js
const nivel = 'info'; // privado ao módulo
module.exports = {
log: (mensagem) => console.log(`[${nivel}] ${mensagem}`)
};
7. Escopo em React: componentes, hooks e reatividade
Em componentes funcionais, cada renderização cria um novo escopo. Isso pode causar stale closures (closures obsoletas) em hooks.
function Temporizador() {
const [segundos, setSegundos] = useState(0);
// PROBLEMA: stale closure
useEffect(() => {
const intervalo = setInterval(() => {
setSegundos(segundos + 1); // segundos está "preso" na primeira renderização
}, 1000);
return () => clearInterval(intervalo);
}, []); // eslint-disable-line
return <div>{segundos} segundos</div>;
}
Soluções:
// Solução 1: dependências corretas
useEffect(() => {
const intervalo = setInterval(() => {
setSegundos(prev => prev + 1); // forma funcional
}, 1000);
return () => clearInterval(intervalo);
}, []);
// Solução 2: useRef para valores mutáveis
const segundosRef = useRef(segundos);
segundosRef.current = segundos;
useEffect(() => {
const intervalo = setInterval(() => {
console.log(segundosRef.current); // sempre o valor atual
}, 1000);
return () => clearInterval(intervalo);
}, []);
8. Depuração e boas práticas finais
Ferramentas de depuração:
- DevTools do navegador: Aba Sources → Scope mostra a cadeia de escopos atual.
- Node.js debugger: Use node inspect ou debugger; no código.
- console.log com contexto: console.log({ variavel }) para ver nome e valor.
Regras de ouro:
1. Prefira const por padrão, use let quando precisar reatribuir, evite var.
2. Minimize o escopo: declare variáveis o mais próximo possível de onde são usadas.
3. Evite globais: use módulos (ES6 ou CommonJS) para encapsulamento.
4. Cuidado com closures em loops: use let ou funções de fábrica.
5. Em React, respeite as dependências dos hooks: o ESLint plugin react-hooks/exhaustive-deps é seu amigo.
Checklist para evitar bugs de escopo:
- [ ] Variáveis globais são intencionais e mínimas?
- [ ] var foi substituído por let/const?
- [ ] Closures em loops usam let ou IIFE?
- [ ] Hooks React têm dependências corretas?
- [ ] Módulos Node.js não vazam variáveis para o escopo global?
Referências
- MDN Web Docs: Escopo — Documentação oficial da Mozilla sobre escopo em JavaScript, com exemplos detalhados de escopo global, local e de bloco.
- JavaScript.info: Closures — Tutorial completo e interativo sobre closures em JavaScript, com exercícios práticos e explicações visuais da cadeia de escopos.
- React Documentation: Hooks and Closures — Seção oficial da documentação do React explicando como closures funcionam com hooks e como evitar stale closures.
- Node.js Documentation: Modules — Documentação oficial do Node.js sobre o sistema de módulos CommonJS e como ele gerencia escopo automaticamente.
- Exploring JS: Variable Scope — Capítulo do livro "Exploring JS" do Dr. Axel Rauschmayer sobre escopo de variáveis, hoisting e temporal dead zone.
- W3Schools: JavaScript Scope — Tutorial introdutório sobre escopo em JavaScript com exemplos práticos de escopo global, local e de bloco.