Escopo em JavaScript
O escopo determina onde uma variável pode ser acessada. Pense como um documento de identidade — seu RG só funciona dentro do seu estado, e sua habilitação não serve no exterior. Variáveis funcionam igual: quando você sai do escopo delas, ficam inacessíveis.
📖 Resumo
Escopo Global
Variáveis declaradas com var, let ou const no nível mais externo têm escopo global e podem ser acessadas de qualquer lugar.
<script>
var globalVar = "Sou uma variável global";
function test() {
console.log(globalVar); // Acessível
}
test();
</script>
Variáveis globais são práticas, mas podem facilmente poluir o namespace — conforme o projeto cresce, conflitos de nomes se tornam inevitáveis. Minimize o uso de variáveis globais.
Escopo de Função
Variáveis declaradas com var seguem o escopo de função: só existem dentro da função atual e desaparecem fora dela.
<script>
function greet() {
var message = "Olá";
console.log(message); // Funciona
}
greet();
try {
console.log(message); // ReferenceError!
} catch(e) {
console.log("Falha no acesso: " + e.message);
}
</script>
Um erro clássico com o escopo de função do var: usar var em um loop for faz a variável "vazar" após o loop terminar.
Escopo de Bloco
Variáveis declaradas com let e const seguem o escopo de bloco: só existem dentro do {} atual.
<script>
if (true) {
let x = 10;
const y = 20;
var z = 30;
console.log("Dentro do bloco: x=" + x + ", y=" + y + ", z=" + z);
}
console.log("Fora do bloco: z=" + z); // var não é restrita por blocos
try {
console.log(x); // x não é acessível aqui
} catch(e) {
console.log("x não acessível: " + e.message);
}
</script>
Por isso contadores de loop devem usar let em vez de var.
Cadeia de Escopo
Escopos internos podem acessar variáveis de escopos externos — assim como você pode usar coisas de casa na escola (se tiver levado). Ao procurar uma variável, o JavaScript busca do escopo atual para fora, camada por camada, até chegar ao escopo global. Essa cadeia é chamada de cadeia de escopo.
<script>
const cidade = "São Paulo";
function externa() {
const bairro = "Pinheiros";
function interna() {
const rua = "Rua dos Pinheiros";
console.log(rua, bairro, cidade); // Tudo acessível
}
interna();
}
externa();
</script>
Escopos externos não podem acessar variáveis de escopos internos — assim como você não consegue enfiar a mão no bolso de um aluno de fora da sala de aula.
Hoisting
Declarações com var são "elevadas" ao topo do escopo, mas as atribuições não:
<script>
console.log(a); // undefined (não é erro!)
var a = 5;
// Ordem real de execução: var a; → console.log(a); → a = 5;
</script>
let e const também sofrem hoisting, mas acessá-los antes da declaração lança um erro — essa região é chamada de Zona Morta Temporal (TDZ):
<script>
try {
console.log(b); // ReferenceError!
} catch(e) {
console.log("Zona morta temporal: " + e.message);
}
let b = 5;
</script>
Resumo: sempre use let e const; evite as armadilhas de hoisting do var.
Closures Explicadas de Forma Intuitiva
Uma closure = uma função + o ambiente de variáveis onde ela nasceu. É como alguém que saiu da cidade natal, mas continua lembrando como era — mesmo que a cidade tenha mudado depois, a memória permanece a mesma.
<script>
function criarContador() {
let contagem = 0; // Esta variável fica "trancada" dentro da closure
return function() {
contagem++;
return contagem;
};
}
const contador = criarContador();
console.log(contador()); // 1
console.log(contador()); // 2
console.log(contador()); // 3
// A variável contagem não foi destruída porque a função retornada ainda "lembra" dela
</script>
criarContador já terminou de executar — logicamente, contagem deveria ser destruída. Mas a função retornada segura contagem com força, então ela sobrevive. Isso é uma closure.
Casos Práticos de Uso de Closures
- Contadores: Registrar estado sem variáveis globais (como mostrado acima)
- Variáveis privadas: Variáveis dentro de closures não podem ser acessadas diretamente de fora — apenas pelas funções retornadas
- Funções de callback: Manter referências a variáveis em listeners de eventos e temporizadores
Exemplo: var vs let em Loops
<!DOCTYPE html>
<html lang="pt-br">
<head>
<meta charset="UTF-8">
<title>var vs let</title>
<style>
body { font-family: sans-serif; padding: 20px; }
.block { margin: 16px 0; padding: 12px; background: #f5f5f5; border-radius: 6px; }
.block h3 { margin-top: 0; }
.var-result, .let-result { margin: 8px 0; font-weight: bold; }
.var-result { color: #d9534f; }
.let-result { color: #5cb85c; }
</style>
</head>
<body>
<h2>var vs let: Diferenças de Escopo em Loops</h2>
<div id="output"></div>
<script>
const resultadosVar = [];
const resultadosLet = [];
for (var i = 0; i < 3; i++) {
resultadosVar.push(`Dentro do loop: i = ${i}`);
}
resultadosVar.push(`Fora do loop: i = ${i} (var vazou!)`);
for (let j = 0; j < 3; j++) {
resultadosLet.push(`Dentro do loop: j = ${j}`);
}
resultadosLet.push(`Acessando j fora do loop → Referencelet (let não vaza)`);
document.getElementById("output").innerHTML = `
<div class="block">
<h3>Declaração com var</h3>
${resultadosVar.map(r => `<div class="var-result">${r}</div>`).join("")}
</div>
<div class="block">
<h3>Declaração com let</h3>
${resultadosLet.map(r => `<div class="let-result">${r}</div>`).join("")}
</div>
`;
</script>
</body>
</html>
Exemplo: Demonstração da Cadeia de Escopo
<!DOCTYPE html>
<html lang="pt-br">
<head>
<meta charset="UTF-8">
<title>Cadeia de Escopo</title>
<style>
body { font-family: sans-serif; padding: 20px; }
.scope { margin: 12px 0; padding: 12px; border-left: 4px solid #4a90d9; background: #f0f7ff; border-radius: 0 6px 6px 0; }
.scope-inner { margin: 12px 0 12px 20px; padding: 12px; border-left: 4px solid #5cb85c; background: #f0fff0; border-radius: 0 6px 6px 0; }
.scope-innermost { margin: 12px 0 12px 20px; padding: 12px; border-left: 4px solid #f0ad4e; background: #fffdf0; border-radius: 0 6px 6px 0; }
code { background: rgba(0,0,0,0.06); padding: 2px 6px; border-radius: 3px; }
</style>
</head>
<body>
<h2>Cadeia de Escopo: Buscando Variáveis de Dentro para Fora</h2>
<div id="output"></div>
<script>
const pais = "Brasil";
function externa() {
const estado = "São Paulo";
function media() {
const cidade = "São Paulo";
function interna() {
const bairro = "Pinheiros";
const resultado = `${bairro}, ${cidade}, ${estado}, ${pais}`;
return resultado;
}
return interna();
}
return media();
}
const final = externa();
document.getElementById("output").innerHTML = `
<div class="scope">
<strong>Escopo Global</strong>: pais = <code>"${pais}"</code>
<div class="scope-inner">
<strong>Escopo da Função externa</strong>: estado = <code>"São Paulo"</code>
<div class="scope-innermost">
<strong>Escopo da Função media</strong>: cidade = <code>"São Paulo"</code>
<div style="margin-top:12px; padding:12px; border-left:4px solid #d9534f; background:#fff0f0; border-radius:0 6px 6px 0;">
<strong>Escopo da Função interna</strong>: bairro = <code>"Pinheiros"</code>
<p>O escopo mais interno pode acessar todas as variáveis externas → <strong>${final}</strong></p>
</div>
</div>
</div>
</div>
`;
</script>
</body>
</html>
Exemplo: Contador com Closure e Variáveis Privadas
<!DOCTYPE html>
<html lang="pt-br">
<head>
<meta charset="UTF-8">
<title>Closures</title>
<style>
body { font-family: sans-serif; padding: 20px; text-align: center; }
.counter { display: inline-block; padding: 20px; background: #f0f7ff; border-radius: 12px; margin: 12px; }
.count { font-size: 48px; font-weight: bold; color: #4a90d9; }
button { padding: 10px 24px; font-size: 16px; border: none; border-radius: 6px; cursor: pointer; background: #4a90d9; color: #fff; margin: 4px; }
button:hover { background: #357abd; }
.reset { background: #d9534f; }
.reset:hover { background: #c9302c; }
.info { color: #888; font-size: 14px; margin-top: 8px; }
</style>
</head>
<body>
<h2>Contador com Closure</h2>
<div class="counter">
<div class="count" id="display">0</div>
<button id="increment">+1</button>
<button id="decrement">-1</button>
<button id="reset" class="reset">Zerar</button>
<div class="info">A variável contagem é protegida pela closure — não pode ser modificada diretamente de fora</div>
</div>
<script>
function criarContador() {
let contagem = 0;
return {
incrementar() { return ++contagem; },
decrementar() { return --contagem; },
zerar() { contagem = 0; return contagem; },
obterContagem() { return contagem; }
};
}
const contador = criarContador();
const display = document.getElementById("display");
function atualizarDisplay() {
display.textContent = contador.obterContagem();
}
document.getElementById("increment").addEventListener("click", function() {
contador.incrementar();
atualizarDisplay();
});
document.getElementById("decrement").addEventListener("click", function() {
contador.decrementar();
atualizarDisplay();
});
document.getElementById("reset").addEventListener("click", function() {
contador.zerar();
atualizarDisplay();
});
</script>
</body>
</html>
Exemplo: Comparação de Hoisting
<!DOCTYPE html>
<html lang="pt-br">
<head>
<meta charset="UTF-8">
<title>Hoisting</title>
<style>
body { font-family: sans-serif; padding: 20px; }
.compare { display: flex; gap: 20px; margin: 16px 0; }
.col { flex: 1; padding: 16px; border-radius: 8px; }
.col-var { background: #fff0f0; border: 2px solid #d9534f; }
.col-let { background: #f0fff0; border: 2px solid #5cb85c; }
h3 { margin-top: 0; }
code { background: rgba(0,0,0,0.06); padding: 2px 6px; border-radius: 3px; }
pre { background: #fff; padding: 12px; border-radius: 6px; overflow-x: auto; }
</style>
</head>
<body>
<h2>Hoisting: var vs let</h2>
<div id="output"></div>
<script>
var varAntes = "Acesso antes da declaração var → " + typeof aVar;
var aVar = 5;
var varDepois = "Acesso após a declaração var → " + aVar;
let letDepois = 5;
let letAntes = "Acesso antes da declaração let → lança ReferenceError!";
document.getElementById("output").innerHTML = `
<div class="compare">
<div class="col col-var">
<h3>var (hoisting sem atribuição)</h3>
<pre><code>console.log(typeof aVar); // ${varAntes}
var aVar = 5;
console.log(aVar); // ${varDepois}</code></pre>
<p>Sem erro, mas você recebe <code>undefined</code> — um bug mais sutil!</p>
</div>
<div class="col col-let">
<h3>let (zona morta temporal)</h3>
<pre><code>console.log(bVar); // ${letAntes}
let bVar = 5;</code></pre>
<p>Lança erro imediatamente — você detecta o problema na hora!</p>
</div>
</div>
`;
</script>
</body>
</html>
❓ Perguntas Frequentes
P: Por que usar
varem um loopforfaz todos os callbacks mostrarem o mesmo valor? R: Porquevartem escopo de função — depois que o loop termina, existe apenas umi, e todos os callbacks compartilham ele (que já vale o valor final do loop). Comlet, cada iteração recebe sua própria cópia independente dei.
P: Causam vazamento de memória as closures? R: Closures em si não são vazamentos — elas mantêm referências às variáveis intencionalmente. Um vazamento real acontece quando você não precisa mais de uma closure, mas ela ainda é mantida por outra cadeia de referências, impedindo a coleta de lixo. A solução: atribuir
nullà referência.
P: Devo usar
letouconst? R: Useconstpor padrão; só useletquando precisar reatribuir. Esse hábito evita que você modifique acidentalmente variáveis que não deveriam mudar.
📝 Atividades
- Escreva uma função
criarSaudacao(saudacao)que retorne uma função que, ao receber um nome, retornesaudacao + ", " + nome. Crie duas funções de saudação com saudações diferentes e verifique que não interferem uma na outra. - Escreva uma função
criarCarteira(saldoInicial)que retorne três métodos:depositar,sacareobterSaldo. Osaldonão deve ser acessível diretamente de fora (variável privada) — só pode ser manipulado por esses métodos. - Explique a saída do código abaixo e o motivo:
<script>
for (var i = 0; i < 3; i++) {
setTimeout(function() { console.log(i); }, 100);
}
</script>
Depois modifique para exibir 0, 1, 2.



