为什么循环分配最后一个索引元素的引用?

时间:2014-01-31 18:15:38

标签: javascript arrays dom for-loop

我想为所有标记添加一个事件监听器,每个标记都会在触发偶数时将其作为参数传递给自身。这是我写的函数:

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. 使用onkeyup处理程序添加isNumberOrDot(event, tagId)事件侦听器。
  4. 问题

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

    问题

    代码/逻辑有什么问题?它怎么能修复?

    注意

    当然这个问题与循环中的JavaScript Closure有关,而这个问题可能有更一般的答案,它特定于正在使用的事件监听器。对于更高级的开发人员,可能很容易将常规解决方案应用于此问题。但对我来说,其他解决方案仍未提供完整的解释,甚至无法解决。

    提前谢谢。

1 个答案:

答案 0 :(得分:6)

因为实际事件在您的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);
    }
}