为什么循环会将最后一个索引元素的引用赋值给什么?

3
我想给所有的标签添加事件监听器,当触发事件时,每个标签都传递自身的引用作为参数。这是我编写的函数:
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);
    }
}

基本上,该函数应该执行以下操作:

  1. 将所有具有指定类名的输入标记存储在数组中
  2. 循环遍历该数组,为每个标记添加一个id,并
  3. 使用isNumberOrDot(event, tagId)处理程序添加onkeyup事件监听器。

问题

已添加onkeyup事件,但是每个处理程序始终引用数组的最后一个元素的tagId

问题

代码/逻辑存在什么问题?如何修复?

注释

确保此问题与JavaScript闭包有关,虽然该问题可能具有更一般的答案,但它特定于正在使用的事件侦听器。对于更高级的开发人员,将通用解决方案应用于此问题可能很容易。但是对我来说,其他解决方案仍未提供完整的解释甚至无法正常工作。

谢谢您的帮助。


1
这是因为你正在处理闭包问题。 - frenchie
1
这个问题已经被问了无数次。如果变量的值将在回调中被访问,那么您需要在每次循环迭代中为闭包绑定变量的值。 - Andrew Mao
谢谢您指引我正确的方向。我不知道要使用哪些关键词来找到解决方案。在得到答案后,我会适当地标记我的问题为重复。 - sargas
@andrew 那不是很精确。事件处理程序已经是闭包了。解决方案是通过执行一个函数来创建一个新的作用域(无论这是否涉及另一个闭包都没有关系)。 - Felix Kling
1个回答

7
因为实际事件发生在你的for循环已经运行结束后的某个时间,因此它的索引处于最后一个值,函数中的任何局部变量如tagId也处于其最后一个值。你需要创建某种保存每个事件处理程序的itagId值的闭包,以便它们各自可以访问自己的值。
有几种不同的方法可以做到这一点,但所有方法都涉及将i值传递给每个事件处理程序的函数。
以下是使用IIFE(立即调用函数表达式)的一个示例:
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);
        })();
    }
}

更常见的做法是将for循环中的索引传递到闭包中,在事件处理程序内部进行任何计算(虽然两种方法都可以),像这样:

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

1
那就是我需要的解决方案。谢谢您的解释。很遗憾,这个问题被标记为其他问题的重复,而这些问题的答案并没有为这种特定情况提供足够的内部信息。尽管它们针对相同的主题。谢谢。 - sargas
1
@sargas - 是的,这是一个常见的根本问题,经常被问到,但最好的解决方案通常因不同情况而异,因此当“重复”真正是回答问题的最佳方式时并不总是明显的。无论如何,很高兴你现在知道如何解决你的问题了。 - jfriend00

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接