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
forcomvarque resulta em todas as funções imprimindo 3. - Versão 2: loop
forcomletque resulta em 0, 1, 2. - Versão 3: loop
forcomvare 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
- Versão 1: Usamos
var i. Comovartem escopo de função (ou global), há apenas umicompartilhado. As três funções closure capturam o mesmoi. Quando chamadas após o loop,ivale 3. - Versão 2: Usamos
let j.lettem escopo de bloco. A cada iteração, uma nova variáveljé criada e capturada pela closure. Assim, cada função vê o valor da sua iteração. - Versão 3: Usamos
var k, mas envolvemos a criação da função em uma IIFE que recebekcomo parâmetroindex. A IIFE cria um novo escopo a cada chamada, eindexé uma cópia do valor deknaquela iteração. - 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
vardentro do loop cria nova variável: não cria, é a mesma. - Esquecer de passar
kpara a IIFE: se a IIFE não receber parâmetro, ela ainda capturarákdo escopo externo.
Isso resultaria em 3, 3, 3.for (var k = 0; k < 3; k++) { (function() { funcsIIFE.push(function() { console.log(k); }); })(); } - Usar
letmas tentar acessar fora do bloco:jnã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