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

IT技术 javascript arrays dom for-loop
2021-01-16 13:09:23

我想为我的所有标签添加一个事件侦听器,每个标签在触发偶数时传递对自身的引用作为参数。这是我写的函数:

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)处理程序事件侦听器

问题

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

问题

代码/逻辑有什么问题?以及如何修复?

笔记

当然,这个问题与循环中的 JavaScript 闭包有关,虽然这个问题可以有一个更一般的答案,但它特定于正在使用的事件侦听器。对于更高级的开发人员,可能很容易将通用解决方案应用于此问题。但对我来说,其他解决方案仍然没有提供完整的解释,甚至没有奏效。

先感谢您。

1个回答

因为实际事件发生在您的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);
    }
}
@sargas - 是的,这是一个常见的根本问题,被问到很多,但最佳解决方案通常因不同情况而有所不同,因此当“重复”确实是回答问题的最佳方式时并不总是很明显。无论如何,很高兴您现在知道如何解决您的问题。
2021-03-29 13:09:23
那就是我需要的解决方案。谢谢你的解释。太糟糕了,这个问题被标记为其他问题的重复,这些问题的答案没有为这种特定情况提供足够的内部信息。即使他们针对同一主题。谢谢你。
2021-04-07 13:09:23