Um pouco atrasado para a festa, mas eu estava explorando essa questão hoje e notei que muitas das respostas não abordam completamente como o Javascript trata os escopos, que é essencialmente o que isso se resume.
Assim, como muitos outros mencionados, o problema é que a função interna está referenciando a mesma i
variável. Então, por que simplesmente não criamos uma nova variável local a cada iteração e, em vez disso, temos a função interna como referência?
//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};
var funcs = {};
for (var i = 0; i < 3; i++) {
var ilocal = i; //create a new local variable
funcs[i] = function() {
console.log("My value: " + ilocal); //each should reference its own local variable
};
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
Assim como antes, onde cada função interna produziu o último valor atribuído i
, agora cada função interna gera apenas o último valor atribuído ilocal
. Mas cada iteração não deveria ter a sua ilocal
?
Acontece que esse é o problema. Cada iteração está compartilhando o mesmo escopo; portanto, todas as iterações após a primeira são substituídas ilocal
. Do MDN :
Importante: JavaScript não tem escopo de bloco. As variáveis introduzidas com um bloco têm o escopo definido para a função ou script que os contém e os efeitos de defini-las persistem além do próprio bloco. Em outras palavras, as instruções de bloco não introduzem um escopo. Embora os blocos "independentes" sejam uma sintaxe válida, você não deseja usar blocos independentes no JavaScript, porque eles não fazem o que você pensa que fazem, se você pensa que eles fazem algo parecido com esses blocos em C ou Java.
Reiterado para enfatizar:
JavaScript não tem escopo de bloco. Variáveis introduzidas com um bloco têm escopo definido para a função ou script que contém
Podemos ver isso verificando ilocal
antes de declará-lo em cada iteração:
//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};
var funcs = {};
for (var i = 0; i < 3; i++) {
console.log(ilocal);
var ilocal = i;
}
É exatamente por isso que esse bug é tão complicado. Mesmo que você esteja redeclarando uma variável, o Javascript não emitirá um erro e o JSLint nem emitirá um aviso. É também por isso que a melhor maneira de resolver isso é tirar proveito dos fechamentos, que é essencialmente a idéia de que, em Javascript, as funções internas têm acesso a variáveis externas porque os escopos internos "incluem" os escopos externos.
Isso também significa que as funções internas "mantêm" as variáveis externas e as mantêm vivas, mesmo que a função externa retorne. Para utilizar isso, criamos e chamamos uma função wrapper puramente para criar um novo escopo, declarar ilocal
no novo escopo e retornar uma função interna que usa ilocal
(mais explicações abaixo):
//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};
var funcs = {};
for (var i = 0; i < 3; i++) {
funcs[i] = (function() { //create a new scope using a wrapper function
var ilocal = i; //capture i into a local var
return function() { //return the inner function
console.log("My value: " + ilocal);
};
})(); //remember to run the wrapper function
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
Criar a função interna dentro de uma função wrapper fornece à função interna um ambiente privado que somente ela pode acessar, um "fechamento". Assim, toda vez que chamamos a função wrapper, criamos uma nova função interna com seu próprio ambiente separado, garantindo que as ilocal
variáveis não colidem e sobrescrevam umas às outras. Algumas otimizações menores dão a resposta final que muitos outros usuários de SO deram:
//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};
var funcs = {};
for (var i = 0; i < 3; i++) {
funcs[i] = wrapper(i);
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
//creates a separate environment for the inner function
function wrapper(ilocal) {
return function() { //return the inner function
console.log("My value: " + ilocal);
};
}
Atualizar
Com o ES6 agora mainstream, agora podemos usar a nova let
palavra-chave para criar variáveis com escopo de bloco:
//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};
var funcs = {};
for (let i = 0; i < 3; i++) { // use "let" to declare "i"
funcs[i] = function() {
console.log("My value: " + i); //each should reference its own local variable
};
}
for (var j = 0; j < 3; j++) { // we can use "var" here without issue
funcs[j]();
}
Veja como é fácil agora! Para obter mais informações, consulte esta resposta , da qual minhas informações se baseiam.
funcs
ser uma matriz, se estiver usando índices numéricos? Apenas um aviso.