var funcs = [];
// let's create 3 functions
for (var i = 0; i < 3; i++) {
// and store them in funcs
funcs[i] = function() {
// each should log its value.
console.log("My value: " + i);
};
}
for (var j = 0; j < 3; j++) {
// and now let's run each one to see
funcs[j]();
}
Isso gera o seguinte:
Meu valor: 3
Meu valor: 3
Meu valor: 3
Considerando que eu gostaria que a saída:
Meu valor: 0
Meu valor: 1
Meu valor: 2
O mesmo problema ocorre quando o atraso na execução da função é causado pelo uso de listeners de eventos:
var buttons = document.getElementsByTagName("button");
// let's create 3 functions
for (var i = 0; i < buttons.length; i++) {
// as event listeners
buttons[i].addEventListener("click", function() {
// each should log its value.
console.log("My value: " + i);
});
}
<button>0</button>
<br />
<button>1</button>
<br />
<button>2</button>
… Ou código assíncrono, por exemplo, usando Promises:
// Some async wait function
const wait = (ms) => new Promise((resolve, reject) => setTimeout(resolve, ms));
for (var i = 0; i < 3; i++) {
// Log `i` as soon as each promise resolves.
wait(i * 100).then(() => console.log(i));
}
Edit: Também é aparente no for in
e for of
loops:
const arr = [1,2,3];
const fns = [];
for(i in arr){
fns.push(() => console.log(`index: ${i}`));
}
for(v of arr){
fns.push(() => console.log(`value: ${v}`));
}
for(f of fns){
f();
}
Qual é a solução para esse problema básico?
javascript
loops
closures
nickf
fonte
fonte
funcs
ser uma matriz, se estiver usando índices numéricos? Apenas um aviso.Respostas:
Bem, o problema é que a variável
i
, dentro de cada uma das suas funções anônimas, está vinculada à mesma variável fora da função.Solução clássica: Fechamentos
O que você deseja fazer é vincular a variável dentro de cada função a um valor inalterado separado fora da função:
Como não há escopo de bloco no JavaScript - somente escopo de função - envolvendo a criação da função em uma nova função, você garante que o valor de "i" permaneça como pretendido.
Solução ES5.1: forEach
Com a disponibilidade relativamente ampla da
Array.prototype.forEach
função (em 2015), vale a pena notar que nas situações que envolvem iteração principalmente sobre uma matriz de valores,.forEach()
fornece uma maneira limpa e natural de obter um fechamento distinto para cada iteração. Ou seja, supondo que você tenha algum tipo de matriz contendo valores (referências DOM, objetos, qualquer que seja), e o problema surge ao configurar retornos de chamada específicos para cada elemento, você pode fazer isso:A idéia é que cada chamada da função de retorno de chamada usada com o
.forEach
loop seja seu próprio fechamento. O parâmetro passado para esse manipulador é o elemento da matriz específico para essa etapa específica da iteração. Se for usado em um retorno de chamada assíncrono, ele não colidirá com nenhum dos outros retornos de chamada estabelecidos em outras etapas da iteração.Se você estiver trabalhando no jQuery, a
$.each()
função fornecerá uma capacidade semelhante.Solução ES6:
let
O ECMAScript 6 (ES6) introduz novas
let
econst
palavras - chave cujo escopo é diferente dasvar
variáveis baseadas em. Por exemplo, em um loop com umlet
índice baseado em, cada iteração no loop terá uma nova variáveli
com escopo de loop, portanto seu código funcionará conforme o esperado. Existem muitos recursos, mas eu recomendaria a publicação de escopo de blocos do 2ality como uma excelente fonte de informações.Porém, lembre-se de que o IE9-IE11 e o Edge anteriores ao Edge 14 suportam
let
errado, mas o erro acima não é criado (eles não criam um novo ai
cada vez); portanto, todas as funções acima registrariam 3 como se usássemosvar
. O Edge 14 finalmente acerta.fonte
function createfunc(i) { return function() { console.log("My value: " + i); }; }
ainda não é fechamento porque usa a variáveli
?Function.bind()
é definitivamente preferível usar agora, consulte stackoverflow.com/a/19323214/785541 ..bind()
é "a resposta correta" não está certa. Cada um deles tem seu próprio lugar. Com.bind()
você, não é possível vincular argumentos sem vincular othis
valor. Além disso, você obtém uma cópia doi
argumento sem a capacidade de alterá-lo entre as chamadas, o que às vezes é necessário. Portanto, são construções bem diferentes, sem mencionar que as.bind()
implementações têm sido historicamente lentas. Claro que no exemplo simples funcionaria, mas os fechamentos são um conceito importante para entender, e era disso que se tratava.Tentar:
Editar (2014):
Pessoalmente, acho que a resposta mais recente da
.bind
@ Aust sobre o uso é a melhor maneira de fazer esse tipo de coisa agora. Também há lo-dash / underscore_.partial
quando você não precisa ou quer mexer combind
'sthisArg
.fonte
}(i));
?i
como argumentoindex
para a função.Outra maneira que ainda não foi mencionada é o uso de
Function.prototype.bind
ATUALIZAR
Conforme apontado por @squint e @mekdev, você obtém melhor desempenho criando primeiro a função fora do loop e depois vinculando os resultados ao loop.
fonte
_.partial
.bind()
ficará amplamente obsoleto com os recursos do ECMAScript 6. Além disso, isso realmente cria duas funções por iteração. Primeiro o anônimo, depois o gerado por.bind()
. Melhor uso seria criá-lo fora do loop e depois.bind()
dentro dele.bind
é usado. Adicionei outro exemplo por suas sugestões.this
.Usando uma expressão de função chamada imediatamente , a maneira mais simples e legível de incluir uma variável de índice:
Isso envia o iterador
i
para a função anônima da qual definimos comoindex
. Isso cria um fechamento, onde a variáveli
é salva para uso posterior em qualquer funcionalidade assíncrona no IIFE.fonte
i
é o quê, renomeio o parâmetro function paraindex
.index
vez dei
.var funcs = {}; for (var i = 0; i < 3; i++) { funcs[i] = (function(index) { return function() {console.log('iterator: ' + index);}; })(i); }; for (var j = 0; j < 3; j++) { funcs[j](); }
.forEach()
, mas muitas vezes, quando alguém está começando com uma matriz,forEach()
é uma boa escolha, como:var nums [4, 6, 7]; var funcs = {}; nums.forEach(function (num, i) { funcs[i] = function () { console.log(num); }; });
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?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ídoilocal
. Mas cada iteração não deveria ter a suailocal
?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 :Reiterado para enfatizar:
Podemos ver isso verificando
ilocal
antes de declará-lo em cada iteração:É 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 usailocal
(mais explicações abaixo):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:Atualizar
Com o ES6 agora mainstream, agora podemos usar a nova
let
palavra-chave para criar variáveis com escopo de bloco:Veja como é fácil agora! Para obter mais informações, consulte esta resposta , da qual minhas informações se baseiam.
fonte
let
-const
chave e . Se essa resposta fosse expandida para incluir isso, seria muito mais útil em termos globais na minha opinião.let
e ligada uma explicação mais completai=0; while(i < 100) { setTimeout(function(){ window.open("https://www.bbc.com","_self") }, 3000); setTimeout(function(){ window.open("https://www.cnn.com","_self") }, 3000); i++ }
:? (poderia substituir window.open () por getelementbyid ......)i
em suas funções de tempo limite, de modo que você não precisa de um fechamentoCom o ES6 agora amplamente suportado, a melhor resposta para esta pergunta mudou. O ES6 fornece as palavras
let
-const
chave e para esta circunstância exata. Em vez de brincar com fechamentos, podemos apenas usarlet
para definir uma variável de escopo de loop como esta:val
, em seguida, apontará para um objeto específico para aquela curva específica do loop e retornará o valor correto sem a notação de fechamento adicional. Obviamente, isso simplifica significativamente esse problema.const
é similar alet
restrição adicional de que o nome da variável não pode ser recuperado para uma nova referência após a atribuição inicial.Agora, o suporte ao navegador está disponível para aqueles que têm como alvo as versões mais recentes dos navegadores.
const
/let
são atualmente suportados no Firefox, Safari, Edge e Chrome mais recentes. Ele também é suportado no Node, e você pode usá-lo em qualquer lugar, aproveitando as ferramentas de construção como o Babel. Você pode ver um exemplo de trabalho aqui: http://jsfiddle.net/ben336/rbU4t/2/Documentos aqui:
Porém, lembre-se de que o IE9-IE11 e o Edge anteriores ao Edge 14 suportam
let
errado, mas o erro acima não é criado (eles não criam um novo ai
cada vez); portanto, todas as funções acima registrariam 3 como se usássemosvar
. O Edge 14 finalmente acerta.fonte
Outra maneira de dizer é que o
i
em sua função é vinculado no momento da execução da função, não no momento de criar a função.Quando você cria o fechamento,
i
é uma referência à variável definida no escopo externo, não uma cópia dela como era quando você criou o fechamento. Será avaliado no momento da execução.A maioria das outras respostas fornece maneiras de contornar, criando outra variável que não altera o valor para você.
Apenas pensei em adicionar uma explicação para maior clareza. Para uma solução, pessoalmente, eu iria com a Harto, já que é a maneira mais auto-explicativa de fazê-lo a partir das respostas aqui. Qualquer código postado funcionará, mas eu optaria por uma fábrica de fechamento em vez de ter que escrever uma pilha de comentários para explicar por que estou declarando uma nova variável (Freddy e 1800) ou possuindo uma sintaxe estranha de fechamento incorporado (apphacker).
fonte
O que você precisa entender é que o escopo das variáveis em javascript é baseado na função. Essa é uma diferença importante do que dizer c # onde você tem escopo de bloco e apenas copiar a variável para uma dentro do for funcionará.
Envolvê-lo em uma função que avalia o retorno da função como a resposta do apphacker fará o truque, pois a variável agora tem o escopo da função.
Há também uma palavra-chave let em vez de var, que permitiria o uso da regra de escopo de bloco. Nesse caso, definir uma variável dentro do for faria o truque. Dito isto, a palavra-chave let não é uma solução prática por causa da compatibilidade.
fonte
Aqui está outra variação da técnica, semelhante à de Bjorn (apphacker), que permite atribuir o valor da variável dentro da função em vez de passá-lo como um parâmetro, que às vezes pode ser mais claro:
Observe que, independentemente da técnica usada, a
index
variável se torna um tipo de variável estática, vinculada à cópia retornada da função interna. Ou seja, alterações em seu valor são preservadas entre as chamadas. Pode ser muito útil.fonte
var
linha e areturn
linha não funcionaria? Obrigado!var
ereturn
a variável não seria atribuída antes de retornar a função interna.Isso descreve o erro comum de usar fechamentos em JavaScript.
Uma função define um novo ambiente
Considerar:
Para cada vez que
makeCounter
é chamado,{counter: 0}
resulta em um novo objeto sendo criado. Além disso, uma nova cópia deobj
é criada também para referenciar o novo objeto. Assim,counter1
ecounter2
são independentes um do outro.Fechamentos em loops
Usar um fechamento em um loop é complicado.
Considerar:
Observe que
counters[0]
e nãocounters[1]
são independentes. De fato, eles operam no mesmoobj
!Isso ocorre porque há apenas uma cópia
obj
compartilhada em todas as iterações do loop, talvez por razões de desempenho. Mesmo que{counter: 0}
crie um novo objeto em cada iteração, a mesma cópia deobj
apenas será atualizada com uma referência ao objeto mais novo.A solução é usar outra função auxiliar:
Isso funciona porque variáveis locais no escopo da função diretamente, bem como variáveis de argumento da função, recebem novas cópias na entrada.
fonte
var obj = {counter: 0};
é avaliado antes de qualquer código ser executado, conforme declarado em: MDN var : var declarações, onde quer que ocorram, são processadas antes de qualquer código ser executado.A solução mais simples seria,
Ao invés de usar:
que alerta "2" por 3 vezes. Isso ocorre porque as funções anônimas criadas no loop for compartilham o mesmo fechamento e, nesse fechamento, o valor de
i
é o mesmo. Use isso para impedir o fechamento compartilhado:A idéia por trás disso é encapsular todo o corpo do loop for com uma IIFE (Expressão de Função Imediatamente Invocada) e passar
new_i
como parâmetro e capturá-lo comoi
. Como a função anônima é executada imediatamente, oi
valor é diferente para cada função definida dentro da função anônima.Essa solução parece se adequar a qualquer problema, pois exigirá alterações mínimas no código original que sofrem com esse problema. De fato, isso ocorre por design, não deve ser um problema!
fonte
tente este mais curto
sem matriz
sem extra para loop
http://jsfiddle.net/7P6EN/
fonte
Aqui está uma solução simples que usa
forEach
(trabalha no IE9):Impressões:
fonte
O principal problema com o código mostrado pelo OP é que
i
nunca é lido até o segundo loop. Para demonstrar, imagine ver um erro dentro do códigoO erro realmente não ocorre até que
funcs[someIndex]
seja executado()
. Usando essa mesma lógica, deve ficar aparente que o valor dei
também não é coletado até este ponto. Uma vez que os acabamentos de ansa originais,i++
trazi
para o valor de3
que resulta na condição dei < 3
falha e o ciclo termina. Nesse ponto,i
é3
e quandofuncs[someIndex]()
é usado ei
avaliado, é 3 - sempre.Para superar isso, você deve avaliar
i
como é encontrado. Observe que isso já aconteceu na forma defuncs[i]
(onde existem três índices exclusivos). Existem várias maneiras de capturar esse valor. Uma é passá-lo como um parâmetro para uma função que é mostrada de várias maneiras já aqui.Outra opção é construir um objeto de função que possa fechar sobre a variável. Isso pode ser realizado assim
jsFiddle Demo
fonte
As funções JavaScript "fecham" o escopo ao qual elas têm acesso após a declaração e mantêm o acesso a esse escopo mesmo quando as variáveis nesse escopo são alteradas.
Cada função na matriz acima fecha sobre o escopo global (global, simplesmente porque esse é o escopo em que são declarados).
Posteriormente, essas funções são chamadas registrando o valor mais atual
i
no escopo global. Essa é a mágica e a frustração do fechamento."As funções JavaScript fecham o escopo em que são declaradas e mantêm o acesso a esse escopo, mesmo que os valores das variáveis dentro desse escopo sejam alterados."
O uso em
let
vez devar
resolve isso, criando um novo escopo sempre que ofor
loop é executado, criando um escopo separado para cada função fechar. Várias outras técnicas fazem a mesma coisa com funções extras.(
let
torna o escopo do bloco de variáveis. Os blocos são indicados por chaves, mas no caso do loop for, a variável de inicialização,i
no nosso caso, é considerada declarada entre chaves.)fonte
i
está sendo definido no escopo global. Quando ofor
loop termina a execução, o valor global dei
agora é 3. Portanto, sempre que essa função é chamada na matriz (usando, digamosfuncs[j]
), ai
função nessa referência faz referência ài
variável global (que é 3).Após ler várias soluções, gostaria de acrescentar que o motivo pelo qual essas soluções funcionam é confiar no conceito de cadeia de escopo . É a maneira como o JavaScript resolve uma variável durante a execução.
var
e seusarguments
.window
.No código inicial:
Quando
funcs
é executada, a cadeia de escopo seráfunction inner -> global
. Como a variáveli
não pode ser encontrada emfunction inner
(nem declarada usandovar
nem passada como argumentos), ela continua pesquisando, até que o valor dei
seja encontrado no escopo global que éwindow.i
.Ao envolvê-lo em uma função externa, defina explicitamente uma função auxiliar como o harto ou use uma função anônima como Bjorn :
Quando
funcs
é executado, agora a cadeia de escopo seráfunction inner -> function outer
. Esse tempoi
pode ser encontrado no escopo da função externa, que é executada 3 vezes no loop for, cada vez que o valor éi
vinculado corretamente. Não usará o valor dewindow.i
quando executado internamente.Mais detalhes podem ser encontrados aqui
Inclui o erro comum na criação de fechamento no loop como o que temos aqui, bem como o motivo pelo qual precisamos de fechamento e a consideração do desempenho.
fonte
Array.prototype.forEach(function callback(el) {})
funcionarem naturalmente: o retorno de chamada transmitido naturalmente forma o escopo de empacotamento com el associado corretamente em cada iteração deforEach
. Assim, cada função interna definida no retorno de chamada será capaz de usar o direitoel
valorCom os novos recursos do escopo do nível de bloco do ES6, é gerenciado:
O código na pergunta do OP é substituído por em
let
vez devar
.fonte
const
fornece o mesmo resultado e deve ser usado quando o valor de uma variável não for alterado. No entanto, o uso deconst
dentro do inicializador do loop for é implementado incorretamente no Firefox e ainda precisa ser corrigido. Em vez de ser declarado dentro do bloco, ele é declarado fora do bloco, o que resulta em uma redeclaração para a variável, que por sua vez resulta em um erro. O uso delet
dentro do inicializador é implementado corretamente no Firefox, por isso não precisa se preocupar.Estou surpreso que ninguém ainda tenha sugerido o uso da
forEach
função para melhor evitar (re) usar variáveis locais. Na verdade, não estou mais usandofor(var i ...)
por esse motivo.// editado para usar em
forEach
vez do mapa.fonte
.forEach()
é uma opção muito melhor se você realmente não estiver mapeando nada, e Daryl sugeriu isso sete meses antes da publicação, para que não haja nada para se surpreender.Esta pergunta realmente mostra a história do JavaScript! Agora podemos evitar o escopo do bloco com funções de seta e manipular loops diretamente de nós DOM usando métodos Object.
fonte
A razão pela qual seu exemplo original não funcionou é que todos os fechamentos que você criou no loop referenciaram o mesmo quadro. Com efeito, ter 3 métodos em um objeto com apenas uma única
i
variável. Todos eles imprimiram o mesmo valor.fonte
Primeiro de tudo, entenda o que há de errado com este código:
Aqui, quando a
funcs[]
matriz está sendo inicializada,i
está sendo incrementada, afuncs
matriz é inicializada e o tamanho dafunc
matriz se torna 3, entãoi = 3,
. Agora, quando ofuncs[j]()
é chamado, ele está novamente usando a variáveli
, que já foi incrementada para 3.Agora, para resolver isso, temos muitas opções. Abaixo estão dois deles:
Podemos inicializar
i
comlet
ou inicializar uma nova variávelindex
comlet
e torná-la igual ai
. Portanto, quando a chamada estiver sendo feita,index
ela será usada e seu escopo terminará após a inicialização. E para ligar,index
será inicializado novamente:Outra opção pode ser a de introduzir um
tempFunc
que retorne a função real:fonte
Use a estrutura de fechamento , isso reduziria seu loop for extra. Você pode fazer isso em um único loop for:
fonte
Caso1 : usando
var
Agora abra a janela do console do Chrome pressionando F12 e atualize a página. Gastar a cada 3 funções dentro da matriz.Você verá uma propriedade chamada
[[Scopes]]
.Expandir essa. Você verá um objeto de matriz chamado"Global"
, expanda esse. Você encontrará uma propriedade'i'
declarada no objeto com o valor 3.Conclusão:
'var'
fora de uma função, ela se torna uma variável global (você pode verificar digitandoi
ouwindow.i
na janela do console. Ele retornará 3).console.log("My value: " + i)
pega o valor do seuGlobal
objeto e exibe o resultado.CASE2: usando let
Agora substitua o
'var'
por'let'
Faça a mesma coisa, vá para os escopos. Agora você verá dois objetos
"Block"
e"Global"
. Agora expanda oBlock
objeto, você verá que 'i' está definido lá, e o estranho é que, para todas as funções, o valor sei
for diferente (0, 1, 2).Conclusão:
Quando você declara uma variável usando
'let'
mesmo fora da função, mas dentro do loop, essa variável não será uma variável Global, ela se tornará umaBlock
variável de nível que está disponível apenas para a mesma função. Esse é o motivo, estamos obtendo valoresi
diferentes para cada função quando invocamos as funções.Para obter mais detalhes sobre o quão mais próximo funciona, acesse o incrível tutorial em vídeo https://youtu.be/71AtaJpJHw0
fonte
Você pode usar um módulo declarativo para listas de dados como query-js (*). Nessas situações, acho pessoalmente uma abordagem declarativa menos surpreendente
Você poderia usar seu segundo loop e obter o resultado esperado ou poderia fazer
(*) Eu sou o autor do query-js e, portanto, inclinado a usá-lo, portanto, não tome minhas palavras como uma recomendação para a referida biblioteca apenas para a abordagem declarativa :)
fonte
Query.range(0,3)
? Isso não faz parte das tags desta pergunta. Além disso, se você usar uma biblioteca de terceiros, poderá fornecer o link da documentação.Eu prefiro usar a
forEach
função, que tem seu próprio fechamento com a criação de um pseudo intervalo:Isso parece mais feio do que em outros idiomas, mas IMHO é menos monstruoso que outras soluções.
fonte
E ainda outra solução: em vez de criar outro loop, basta vincular a
this
função de retorno.Ao vincular isso , resolve o problema também.
fonte
Muitas soluções parecem corretas, mas não mencionam que é chamado de
Currying
padrão de design de programação funcional para situações como aqui. 3-10 vezes mais rápido que o bind, dependendo do navegador.Veja o ganho de desempenho em diferentes navegadores .
fonte
Seu código não funciona, porque o que faz é:
Agora, a pergunta é: qual é o valor da variável
i
quando a função é chamada? Como o primeiro loop é criado com a condição dei < 3
, ele para imediatamente quando a condição é falsa, por isso éi = 3
.Você precisa entender que, no momento em que suas funções são criadas, nenhum código é executado, ele é salvo apenas para mais tarde. E assim, quando são chamados mais tarde, o intérprete os executa e pergunta: "Qual é o valor atual
i
?"Portanto, seu objetivo é primeiro salvar o valor de
i
to function e somente depois salvar a função emfuncs
. Isso pode ser feito, por exemplo, desta maneira:Dessa forma, cada função terá sua própria variável
x
e configuramos issox
para o valor dei
em cada iteração.Essa é apenas uma das várias maneiras de resolver esse problema.
fonte
fonte
Use let (escopo bloqueado) em vez de var.
fonte