Por que o loop está atribuindo uma referência do último elemento de índice para?

Eu quero adicionar um ouvinte de evento a todas as minhas tags, cada uma passando uma referência a si mesma como um parâmetro quando o par é acionado. Aqui está a function que eu escrevi:

function validateDigitsFeature() { // Add the event listeners to input tags // Get the array of input tags var inputTags = document.getElementsByClassName('validateInput'); var tagId; // Loop through them, adding the onkeypress event listener to each one for (var i = 0; i < inputTags.length; i++) { // Give each input element an id tagId = inputTags[i].id = 'input_id_' + i; inputTags[i].addEventListener('keyup', function(){isNumberOrDot(event, tagId);}, false); } } 

Basicamente, a function deve fazer o seguinte:

  1. Armazena todas as tags de input com o nome da class especificado em uma matriz
  2. Faz um loop pelo array, adicionando um id a cada tag e
  3. Adicionando o listener de evento isNumberOrDot(event, tagId) com o isNumberOrDot(event, tagId) .

Problema

O evento onkeyup é adicionado, mas os manipuladores de cada um deles sempre fazem referência ao tagId do último elemento da matriz.

Questão

O que há de errado com o código / lógica? E como pode ser consertado?

Nota

Claro que esse problema está relacionado com loops de fechamento de JavaScript, enquanto essa pergunta poderia ter uma resposta mais geral, é específica para os ouvintes de evento que estão sendo usados. Para desenvolvedores mais avançados, pode ser fácil aplicar a solução geral a esse problema. Mas para mim as outras soluções ainda não forneceram uma explicação completa ou mesmo funcionaram.

Agradeço antecipadamente.

Porque o evento real ocorre em algum momento no futuro, depois que o loop for já terminou de executar e, portanto, seu índice está no último valor e quaisquer variables ​​locais na sua function como tagId também estão no último valor. Você precisa criar algum tipo de fechamento que preserve o valor de i ou tagId exclusivamente para cada manipulador de events, para que cada um tenha access a seu próprio valor.

Existem várias maneiras diferentes de fazer isso, mas todas envolvem a passagem do valor i para uma function para cada manipulador de events.

Aqui está um usando um IIFE (expressão de function invocada imediatamente):

 function validateDigitsFeature() { // Add the event listeners to input tags // Get the array of input tags var inputTags = document.getElementsByClassName('validateInput'); // Loop through them, adding the onkeypress event listener to each one for (var i = 0; i < inputTags.length; i++) { // Give each input element an id (function() { // creates a unique function context for each event handler so the // value of tagId is unique for each event handler var tagId = inputTags[i].id = 'input_id_' + i; inputTags[i].addEventListener('keyup', function(){isNumberOrDot(event, tagId);}, false); })(); } } 

Uma maneira pouco comum de fazer isso é passar o índice do loop for para o encerramento e fazer qualquer cálculo baseado nele dentro do manipulador de events (embora o método funcione bem) assim:

 function validateDigitsFeature() { // Add the event listeners to input tags // Get the array of input tags var inputTags = document.getElementsByClassName('validateInput'); // Loop through them, adding the onkeypress event listener to each one for (var i = 0; i < inputTags.length; i++) { // Give each input element an id (function(index) { // passes the `for` loop index into a function closure // so it is uniquely preserved for each event handler inputTags[index].addEventListener('keyup', function(){ isNumberOrDot(event, inputTags[index].id = 'input_id_' + index); }, false); })(i); } }