404 Not Found

404 Not Found


nginx

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.

HTML
<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.

HTML
<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.

HTML
<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.

HTML
<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:

HTML
<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):

HTML
<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.

HTML
<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


Exemplo: var vs let em Loops

HTML
<!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>
▶ Experimente

Exemplo: Demonstração da Cadeia de Escopo

HTML
<!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>
▶ Experimente

Exemplo: Contador com Closure e Variáveis Privadas

HTML
<!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>
▶ Experimente

Exemplo: Comparação de Hoisting

HTML
<!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>
▶ Experimente

❓ Perguntas Frequentes

P: Por que usar var em um loop for faz todos os callbacks mostrarem o mesmo valor? R: Porque var tem escopo de função — depois que o loop termina, existe apenas um i, e todos os callbacks compartilham ele (que já vale o valor final do loop). Com let, cada iteração recebe sua própria cópia independente de i.

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 let ou const? R: Use const por padrão; só use let quando precisar reatribuir. Esse hábito evita que você modifique acidentalmente variáveis que não deveriam mudar.


📝 Atividades

  1. Escreva uma função criarSaudacao(saudacao) que retorne uma função que, ao receber um nome, retorne saudacao + ", " + nome. Crie duas funções de saudação com saudações diferentes e verifique que não interferem uma na outra.
  2. Escreva uma função criarCarteira(saldoInicial) que retorne três métodos: depositar, sacar e obterSaldo. O saldo não deve ser acessível diretamente de fora (variável privada) — só pode ser manipulado por esses métodos.
  3. Explique a saída do código abaixo e o motivo:
HTML
<script>
for (var i = 0; i < 3; i++) {
  setTimeout(function() { console.log(i); }, 100);
}
</script>

Depois modifique para exibir 0, 1, 2.

100%