¿Por qué el bucle asigna una referencia del último elemento de índice?

Quiero agregar un detector de eventos a todas mis tags, cada una pasando una referencia a sí mismo como un parámetro cuando se activa el par. Aquí está la función que escribí:

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); } } 

Básicamente, la función debe hacer lo siguiente:

  1. Almacene todas las tags de entrada con el nombre de clase especificado en una matriz
  2. Pasa por la matriz, agregando una identificación a cada etiqueta y
  3. Agregar el detector de eventos onkeyup con el isNumberOrDot(event, tagId) .

Problema

El evento onkeyup se agrega, pero los manejadores de cada uno siempre hacen referencia a tagId del último elemento de la matriz.

Pregunta

¿Qué está mal con el código / lógica? ¿Y cómo se puede arreglar?

Nota

Seguro que este problema está relacionado con el Cierre de JavaScript en bucles, mientras que esta pregunta podría tener una respuesta más general, es específica para los oyentes de eventos que se utilizan. Para los desarrolladores más avanzados, podría ser fácil aplicar la solución general a este problema. Pero para mí, las otras soluciones todavía no proporcionaban una explicación completa o incluso funcionaban.

Gracias de antemano.

Debido a que el evento real ocurre en algún momento en el futuro después de que su ciclo for ya se haya ejecutado, su índice está en el último valor y cualquier variable local en su función como tagId también está en su último valor. tagId crear algún tipo de cierre que preserve el valor de i o tagId forma exclusiva para cada controlador de eventos para que cada uno tenga acceso a su propio valor.

Hay varias maneras diferentes de hacerlo, pero todas implican pasar el valor i a una función para cada manejador de eventos.

Aquí hay uno que usa un IIFE (expresión de función invocada inmediatamente):

 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); })(); } } 

Una forma un poco más común de hacer esto es pasar el índice del bucle for al cierre y hacer cualquier cálculo basado en él dentro del controlador de eventos (aunque cualquiera de los métodos funciona bien) de esta manera:

 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); } }