Desafio JavaScript

Loop com closures: var vs let

Enunciado

Crie uma função que retorna um array de funções. Cada função, ao ser chamada, deve imprimir seu índice (0, 1, 2). Use let no loop para obter o comportamento correto. Em seguida, mostre como var causaria um problema e como corrigi-lo com uma IIFE (Immediately Invoked Function Expression).

Requisitos

  • A função criarFuncoesLet deve retornar um array de 3 funções que imprimem 0, 1 e 2 respectivamente.
  • A função criarFuncoesVar deve usar var e demonstrar o problema (todas imprimem 3).
  • A função criarFuncoesVarCorrigido deve usar var com IIFE para corrigir o problema.
  • O programa deve chamar cada função e imprimir os valores na ordem correta.

Código inicial

function criarFuncoesLet() {
  // seu código aqui
}

function criarFuncoesVar() {
  // seu código aqui
}

function criarFuncoesVarCorrigido() {
  // seu código aqui
}

// Testes
console.log('Com let:');
criarFuncoesLet().forEach(fn => fn());

console.log('Com var (problema):');
criarFuncoesVar().forEach(fn => fn());

console.log('Com var corrigido:');
criarFuncoesVarCorrigido().forEach(fn => fn());

Saída esperada

Com let:
0
1
2
Com var (problema):
3
3
3
Com var corrigido:
0
1
2
Ver dica

Com let, cada iteração cria um novo escopo. Com var, o escopo é único. A IIFE cria um novo escopo a cada iteração.

Mostrar solução
function criarFuncoesLet() {
  const funcoes = [];
  for (let i = 0; i < 3; i++) {
    funcoes.push(() => console.log(i)); // cada iteração tem seu próprio i
  }
  return funcoes;
}

function criarFuncoesVar() {
  const funcoes = [];
  for (var i = 0; i < 3; i++) {
    funcoes.push(() => console.log(i)); // todas compartilham o mesmo i
  }
  return funcoes;
}

function criarFuncoesVarCorrigido() {
  const funcoes = [];
  for (var i = 0; i < 3; i++) {
    (function (indice) {
      funcoes.push(() => console.log(indice)); // captura o valor atual
    })(i);
  }
  return funcoes;
}

// Testes
console.log('Com let:');
criarFuncoesLet().forEach(fn => fn());

console.log('Com var (problema):');
criarFuncoesVar().forEach(fn => fn());

console.log('Com var corrigido:');
criarFuncoesVarCorrigido().forEach(fn => fn());

Passo a passo

  1. criarFuncoesLet usa let i no loop. A cada iteração, um novo escopo de bloco é criado, então cada closure captura um i diferente.
  2. criarFuncoesVar usa var i. Como var tem escopo de função, todas as closures compartilham o mesmo i, que após o loop vale 3.
  3. criarFuncoesVarCorrigido usa uma IIFE que recebe i como parâmetro indice, criando um novo escopo a cada chamada.
  4. As funções são armazenadas em um array e retornadas.
  5. Ao chamar cada função, vemos os valores corretos.

Por que funciona

let cria um novo binding a cada iteração do loop, então cada closure captura seu próprio i. Com var, o binding é único e compartilhado, resultando em 3 para todas. A IIFE simula o escopo de bloco criando uma nova função a cada iteração.

Erros comuns

  • Usar var e esperar valores diferentes: todas as funções imprimem o valor final de i.
  • Esquecer de invocar a IIFE: (function(indice) { ... }) sem (i) não executa.
  • Usar let fora do loop: perde o escopo de bloco por iteração.

Outra forma de resolver

Poderia usar forEach ou map, que criam escopos automaticamente:

function criarFuncoesForEach() {
  const funcoes = [];
  [0, 1, 2].forEach(i => {
    funcoes.push(() => console.log(i));
  });
  return funcoes;
}

Essa abordagem é mais idiomática e evita a necessidade de IIFE.

Saída esperada

Com let:
0
1
2
Com var (problema):
3
3
3
Com var corrigido:
0
1
2