Por favor, explique o uso de fechamentos de JavaScript em loops

Eu li uma série de explicações sobre fechamentos e fechamentos dentro de loops. Eu tenho dificuldade em entender o conceito. Eu tenho este código: Existe uma maneira de reduzir o código tanto quanto possível para que o conceito de fechamento possa ser esclarecido. Estou tendo dificuldade em entender a parte em que o i está dentro de dois parênteses. obrigado

 function addLinks () { for (var i=0, link; i<5; i++) { link = document.createElement("a"); link.innerHTML = "Link " + i; link.onclick = function (num) { return function () { alert(num); }; }(i); document.body.appendChild(link); } } window.onload = addLinks; 

AVISO: Resposta longa (ish)

Isso é copiado diretamente de um artigo que escrevi em um wiki interno da empresa:

Pergunta: Como usar corretamente os fechamentos em loops? Resposta rápida: use uma fábrica de funções.

  for (var i=0; i<10; i++) { document.getElementById(i).onclick = (function(x){ return function(){ alert(x); } })(i); } 

ou a versão mais facilmente legível:

  function generateMyHandler (x) { return function(){ alert(x); } } for (var i=0; i<10; i++) { document.getElementById(i).onclick = generateMyHandler(i); } 

Isso muitas vezes confunde as pessoas que são novas em JavaScript ou functional programming. É um resultado de entender mal o que são fechamentos.

Um fechamento não apenas passa o valor de uma variável ou até mesmo uma referência à variável. Um fechamento captura a variável em si! O seguinte bit de código ilustra isso:

  var message = 'Hello!'; document.getElementById('foo').onclick = function(){alert(message)}; message = 'Goodbye!'; 

Clicar no elemento 'foo' irá gerar uma checkbox de alerta com a mensagem: "Adeus!". Por causa disso, usar um fechamento simples em um loop terminará com todos os closures compartilhando a mesma variável e essa variável conterá o último valor atribuído a ela no loop. Por exemplo:

  for (var i=0; i<10; i++) { document.getElementById('something'+i).onclick = function(){alert(i)}; } 

Todos os elementos, quando clicados, gerarão uma checkbox de alerta com o número 10. De fato, se fizermos agora i="hello"; Todos os elementos agora geram um alerta de "Olá"! A variável i é compartilhada entre dez funções MAIS a function / escopo / contexto atual. Pense nisso como uma espécie de variável global privada que apenas as funções envolvidas podem ver.

O que queremos é uma instância dessa variável ou, pelo menos, uma simples referência à variável em vez da própria variável. Felizmente javascript já tem um mecanismo para passar uma referência (para objects) ou valor (para seqüências de caracteres e números): argumentos de function!

Quando uma function é chamada em javascript, os argumentos para essa function são passados ​​por referência, se for um object ou por valor, se for uma string ou um número. Isso é suficiente para quebrar o compartilhamento de variables ​​nos fechamentos.

Assim:

  for (var i=0; i<10; i++) { document.getElementById(i).onclick = (function(x){ /* we use this function expression simply as a factory to return the function we really want to use: */ /* we want to return a function reference so we write a function expression*/ return function(){ alert(x); /* x here refers to the argument of the factory function captured by the 'inner' closure */ } /* The brace operators (..) evaluates an expression, in this case this function expression which yields a function reference. */ })(i) /* The function reference generated is then immediately called() where the variable i is passed */ } 

Eu tenho programado em JavaScript por um longo tempo, e “fechamento em loop” é um tópico muito amplo. Eu suponho que você está falando sobre a prática de usar (function(param) { return function(){ ... }; })(param); dentro de um loop for, a fim de preservar o “valor atual” do loop quando essa function interna mais tarde executa …

O código:

 for(var i=0; i<4; i++) { setTimeout( // argument #1 to setTimeout is a function. // this "outer function" is immediately executed, with `i` as its parameter (function(x) { // the "outer function" returns an "inner function" which now has x=i at the // time the "outer function" was called return function() { console.log("i=="+i+", x=="+x); }; })(i) // execute the "closure" immediately, x=i, returns a "callback" function // finishing up arguments to setTimeout , i*100); } 

Saída:

 i==4, x==0 i==4, x==1 i==4, x==2 i==4, x==3 

Como você pode ver pela saída, todas as funções internas de retorno de chamada apontam para o mesmo i , no entanto, uma vez que cada uma tinha seu próprio 'fechamento', o valor de x é realmente armazenado como o que i estava no momento da function externa execução.

Geralmente, quando você vê esse padrão, você usaria o mesmo nome de variável que o parâmetro e o argumento para a function externa: (function(i){ })(i) por exemplo. Qualquer código dentro dessa function (mesmo se executado posteriormente, como uma function de retorno de chamada) vai se referir a i no momento em que você chamou a "function externa".

Bem, o “problema” com encerramentos em tal caso é que qualquer access a i referenciaria a mesma variável. Isso é devido ao function scope ou lexical scope do ECMA-/Javascripts .

Então, para evitar que todas as chamadas para alert(i); exibiria um 5 (porque após o loop terminar i === 5), você precisa criar uma nova function que invoca-se em tempo de execução.

Para conseguir isto, você precisa criar uma nova function, mais você precisa da parantesia extra no final, para invoke the outer function imediatamente, então link.onclick tem agora a function retornada como referência.

Um encerramento é uma construção na qual você faz referência a uma variável fora do escopo no qual ela está definida. Você costuma falar sobre encerramentos no contexto de uma function.

 var helloFunction; var finished = false; while (!finished) { var message = 'Hello, World!'; helloFunction = function() { alert(message); } finished = true; } helloFunction(); 

Aqui, eu defino a mensagem variável e defino uma function que referencia a mensagem . Quando defino a function para usar mensagem, estou criando um fechamento. Isso significa que helloFunction contém uma referência à mensagem , para que eu possa continuar a usar a mensagem , mesmo fora do escopo (o corpo do loop) onde a mensagem está definida.

Termo aditivo

O (i) entre parênteses é uma chamada de function. O que está acontecendo é:

  1. Você define alguma function (num) {}. Isso é chamado de function anônima , porque é definido em linha e não tem nome.
  2. function (num) pega um argumento inteiro, e retorna uma referência para outra function, que é definida como alert (num)
  3. A function anônima externa é imediatamente chamada, com o argumento i . Então num = i . O resultado desta chamada é uma function que fará alerta (i).
  4. O resultado final é mais ou menos equivalente a: link.onclick = function() { alert(i); }; link.onclick = function() { alert(i); };

Para responder a última parte das suas perguntas. Os dois parênteses invocam a function como qualquer outra function. Por que você faz isso aqui é que você quer manter o que a variável “i” é apenas naquele momento. Então, o que acontece é invocar a function, o i é enviado como um argumento “num”. Uma vez que é invocado, ele se lembrará do valor nume na própria binding de links variables.

Se você não fez isso, todos os cliques do link resultariam em um alerta dizendo “5”

John Resig, fundador da jQuery, tem uma apresentação on-line muito legal explicando isso. http://ejohn.org/apps/learn/

..fredrik