Desafio JavaScript

Closures em loop com var vs let

Enunciado

Crie um array de funções que, ao serem chamadas, imprimem o índice do loop (0, 1, 2). Primeiro, implemente usando var e explique por que não funciona corretamente. Depois, corrija usando let. Por fim, implemente uma terceira versão que funcione com var usando uma IIFE (Immediately Invoked Function Expression).

Requisitos

  • Versão 1: loop for com var que resulta em todas as funções imprimindo 3.
  • Versão 2: loop for com let que resulta em 0, 1, 2.
  • Versão 3: loop for com var e uma IIFE que também resulta em 0, 1, 2.

Código inicial

// Versão 1: var
const funcsVar = [];
for (var i = 0; i < 3; i++) {
  funcsVar.push(function() { console.log(i); });
}
funcsVar[0](); // 3
funcsVar[1](); // 3
funcsVar[2](); // 3

// Versão 2: let
const funcsLet = [];
for (let j = 0; j < 3; j++) {
  funcsLet.push(function() { console.log(j); });
}
funcsLet[0](); // 0
funcsLet[1](); // 1
funcsLet[2](); // 2

// Versão 3: var + IIFE
const funcsIIFE = [];
for (var k = 0; k < 3; k++) {
  (function(index) {
    funcsIIFE.push(function() { console.log(index); });
  })(k);
}
funcsIIFE[0](); // 0
funcsIIFE[1](); // 1
funcsIIFE[2](); // 2

Saída esperada

3
3
3
0
1
2
0
1
2
Ver dica

Com var, a variável do loop é única e compartilhada. Com let, cada iteração tem seu próprio escopo. A IIFE cria um novo escopo a cada iteração.

Mostrar solução
// Versão 1: var - problema de closure compartilhada
const funcsVar = [];
for (var i = 0; i < 3; i++) {
  funcsVar.push(function() { console.log(i); });
}
funcsVar[0](); // 3
funcsVar[1](); // 3
funcsVar[2](); // 3

// Versão 2: let - escopo de bloco por iteração
const funcsLet = [];
for (let j = 0; j < 3; j++) {
  funcsLet.push(function() { console.log(j); });
}
funcsLet[0](); // 0
funcsLet[1](); // 1
funcsLet[2](); // 2

// Versão 3: var + IIFE - cria novo escopo a cada iteração
const funcsIIFE = [];
for (var k = 0; k < 3; k++) {
  (function(index) {
    funcsIIFE.push(function() { console.log(index); });
  })(k);
}
funcsIIFE[0](); // 0
funcsIIFE[1](); // 1
funcsIIFE[2](); // 2

Passo a passo

  1. Versão 1: Usamos var i. Como var tem escopo de função (ou global), há apenas um i compartilhado. As três funções closure capturam o mesmo i. Quando chamadas após o loop, i vale 3.
  2. Versão 2: Usamos let j. let tem escopo de bloco. A cada iteração, uma nova variável j é criada e capturada pela closure. Assim, cada função vê o valor da sua iteração.
  3. Versão 3: Usamos var k, mas envolvemos a criação da função em uma IIFE que recebe k como parâmetro index. A IIFE cria um novo escopo a cada chamada, e index é uma cópia do valor de k naquela iteração.
  4. As funções são armazenadas no array e chamadas depois, demonstrando os diferentes comportamentos.

Por que funciona

Closures capturam variáveis por referência, não por valor. Com var, a referência é a mesma variável do loop. Com let, cada iteração tem uma nova variável. A IIFE simula esse comportamento criando um novo escopo manualmente.

Erros comuns

  • Achar que var dentro do loop cria nova variável: não cria, é a mesma.
  • Esquecer de passar k para a IIFE: se a IIFE não receber parâmetro, ela ainda capturará k do escopo externo.
    for (var k = 0; k < 3; k++) {
      (function() {
        funcsIIFE.push(function() { console.log(k); });
      })();
    }
    
    Isso resultaria em 3, 3, 3.
  • Usar let mas tentar acessar fora do bloco: j não existe fora do loop.

Outra forma de resolver

Usar forEach ou map com let implícito:

const funcs = [0,1,2].map(i => () => console.log(i));

Mais funcional, mas foge do escopo do exercício.

Saída esperada

3
3
3
0
1
2
0
1
2