如何将变量的当前值传递给事件处理程序 (Javascript)

How can I pass the current value of a variable to an event handler (Javascript)

提问人:Randy 提问时间:9/26/2020 最后编辑:Randy 更新时间:9/28/2020 访问量:262

问:

对此有点尴尬,但假设我在一个名为 iBlock 的父元素中有这组“子”元素,并且在一个函数中,我正在为每个子元素添加一个“onclick”事件,就像这样......

someFunction(iBlock) {
    var el, iTotal = iBlock.children.length;
     for (var i= 0; i < iTotal; i++) {
             el=iBlock.children[i];
             el.onclick=function(event){myHandler(this, i)};
             }
    }

因此,给定这样的 onclick 处理程序......

function myHandler(elem, n) {
     alert("number is: " +  n);
    }

有点令人惊讶的是,无论我单击哪个子元素,此示例中的处理程序都会显示小于子元素总数的元素。但我知道总数 - 1 是变量“i”在循环结束前达到的最高数字,并且处理程序不会传递任何东西,只会传递事件发生时变量的值。但是理解这一点并不能帮助我回答这两个问题......

  1. 如果我真的希望 onclick 函数中的第二个参数传递值“i”,那么实际上在 onclick 处理程序 附加,我可以传递什么来代替原始的“i”变量?
  2. 由于 'i' 是由函数中的 'var' 关键字声明的 添加了处理程序,为什么它仍然有一个值(在这种情况下 总计 - 1),安装功能是否返回?如果处理程序显示“未定义”,我会不那么惊讶。我意识到 这是一个“变量范围”的问题,但我敢肯定它是相关的。

关于第一个问题,我尝试传递:i+'x',只是为了把它变成一个字符串,希望它能生成一个唯一的实例。那也行不通......元素“单击”只会触发显示“4x”的处理程序。我还尝试创建第二个变量,作为字符串,然后提取数字部分,并将最终的“值”从新变量传递给 onclick 处理程序,如下所示......

someFunction(iBlock) {
        var el, iTotal = iBlock.children.length;
         for (var i= 0; i < iTotal; i++) {
            el=iBlock.children[i];
            var n= i+'x';  // make 'i' into a new longer string variable
            var r = /\d+/;    // regex, to capturei digits
            n = n.match(r); // convert the string to only contain the digits
            el.onclick=function(event){myHandler(this, n)};
            }
        }

没有变化。处理程序仍然在我单击的任何元素上显示 total-1。

我看过关于这个主题的类似帖子,例如从字符串中提取数字 (JavaScript),但我仍然不明白我的第一个问题的补救措施。

PS(我没有尝试使用“let”而不是“var”创建变量,因为我非常致力于支持某些较旧的浏览器)。编辑:如果可能的话,我想将IE-8包含在那些“旧浏览器”中。我意识到我不能总是这样做,但在这种情况下,我愿意这样做。

编辑:我找到了一个有趣的解决方法,即在添加处理程序的循环中将我自己的变量添加到每个元素中,如下所示......

someFunction(iBlock) {
    var el, iTotal = iBlock.children.length;
     for (var i= 0; i < iTotal; i++) {
             el=iBlock.children[i];
             el.onclick=function(event){myHandler(this, i)};
             el.myIndex=i;
             }
    }

然后当然,在我的处理程序中,无论如何我都在传递“this”,我可以通过检查“this.myIndex”来正确恢复“i”的所需值。我听说这很危险,因为它可能会与未来的 DOM 属性发生冲突。

但这似乎是“笨拙的”,所以我仍然对答案感兴趣!

JavaScript 作用域 按引用传递值

评论

2赞 gp. 9/26/2020
n 不会重新声明,并且事件处理程序基本上指向相同的 n 变量,因此它将获取在执行 r 时分配的最后一个值。试试这个:el.onclick=(function(localn){return function(event){myHandler(this, localn)};})(n);
0赞 Nikola Mitic 9/26/2020
在我看来,您需要使用闭包捕获该 VAR。你试过吗?
0赞 ATD 9/26/2020
在函数中,您声明了一个名为 的变量,您没有为其分配任何值,但您使用 难道不应该先将每个 iBlock 子项分配给 el 变量吗?elel.onclick=....
0赞 Nikola Mitic 9/26/2020
此外,EL 未定义
0赞 Nikola Mitic 9/26/2020
el = iBlock.children[i] 还是什么?

答:

0赞 ATD 9/26/2020 #1

el没有被分配到任何东西。尝试:

someFunction(iBlock) {
    var iTotal = iBlock.children.length;
    for (var i= 0; i < iTotal; i++) {
        iBlock.children[i].onclick=function(event){myHandler(this, i)};
    }
}

评论

0赞 Randy 9/26/2020
不好意思。。。这在评论中指出了,我已经对其进行了编辑,以显示我如何将“el”分配给当前索引的子项。但是无论是我的编辑还是您的编辑都无法解决这样一个事实,即在处理程序中,“i”将始终显示 total-1
1赞 Nikola Mitic 9/26/2020
@Randy您需要创建一个闭包来捕获该迭代器值,请查看我的答案。如果你能只使用 let,你就不需要这个了。因为 let 没有被吊起,所以它被词汇舀起,所以这不是问题
0赞 Randy 9/26/2020
@NikolaMitic 是的,当我第一次意识到“let”在 IE-8 中不起作用时,我非常沮丧。但无论如何,现在做了一些研究,我发现我发现的向元素添加属性的解决方案比我想象的要广泛。它在事件处理程序中作为“this”的属性提供,即使它只是通过将其分配给“i”变量在循环中创建的,该属性也反映了添加它时的值“i”,这是完美的。我看到的唯一警告是我需要避免添加可能与将来的标准属性冲突的属性名称。
2赞 Nikola Mitic 9/26/2020 #2

您需要使用 IIFE 创建一个闭包来捕获迭代器变量。

有关闭合的更多信息

关于IIFE的更多信息

function handleClick(element, n) {
    alert("number is: " +  n);
}

function iterateOverChildNOde(parentNode) {
  var el = parentNode.children
  var iTotal = parentNode.children.length;
 
  
    for (var i= 0; i < iTotal; i++) {

    (function(iterator){
      el[i].onclick=function(event){handleClick(el[iterator], iterator)};
      })(i)
  }
}

iterateOverChildNOde(document.getElementById('parent'));
<div id="parent">
  <div class="child">C</div>
  <div class="child">L</div>
  <div class="child">I</div>
  <div class="child">I</div>
  <div class="child">C</div>
  <div class="child">L</div>
</div>

此外,您需要意识到这是一种不好的做法。您应该只将事件处理程序附加到父元素,然后使用 e.target。

有关事件委派的更多信息

  function handleClick(element, n) {
        alert("number is: " +  n);
    }

document.getElementById('parent').onclick=function(e){handleClick(e.target, Array.prototype.indexOf.call(this.children, e.target))}
    <div id="parent">
      <div class="child">C</div>
      <div class="child">L</div>
      <div class="child">I</div>
      <div class="child">I</div>
      <div class="child">C</div>
      <div class="child">L</div>
    </div>

评论

0赞 Randy 9/26/2020
一个问题是,正如我所提到的,我致力于支持较旧的浏览器,我应该澄清一下,如果可能的话,我喜欢包含IE-8。看起来 IFFE 开始在 IE9 中部分工作。而且,根据我自己的理解,我已经读过,IEFE 函数是在声明时调用的。这些人一旦被宣布就被“召唤”了吗?如果是这样,在我实际单击元素之前,我什么时候希望(例如)运行“onclick”处理程序?
0赞 Nikola Mitic 9/26/2020
我不知道 IIFE 在 IE-8 中不起作用,IIFE 是 JS 语言的一部分,所以我认为它应该可以工作。你是对的,IIFE 将在宣布后立即运行,这没有错。onClick 处理程序是一个处理程序,或者更确切地说是回调函数,它只会在浏览器调度点击事件时才会发生,所以当用户点击它时。
0赞 Nikola Mitic 9/26/2020
有什么能阻止你不使用 for 循环,不附加所有这些事件侦听器,而只是像我在第二个示例中向你展示的那样使用 e.target?
0赞 Randy 9/26/2020
我确实在 MDM Web 文档图表上找到了 IE-9+ 支持,但为了解决您的问题,这是我正在翻新的有点“遗留”的网站上导航菜单系统的一部分。由于导航系统可能会经常更新,因此我以这样一种方式创建它,以便将数组中的模板和项目转换为适当的图标、文本链接和操作。可以想象,添加到数组比更新硬代码要麻烦得多,也不容易出错。我知道没有办法在不丢失某些浏览器的情况下更新整个遗留站点,但导航系统必须工作。谢谢
1赞 Nikola Mitic 9/26/2020
我明白了,那么 IIFE(我发布的第一个示例)将为您工作,因为它们不是 Web API 的一部分,而是语言本身。我什至相信事件委派(第二个示例)可以在 IE-8 上运行,但我不确定
2赞 Jacob 9/26/2020 #3

如注释中所述,问题在于您的内联单击处理程序函数引用了闭包中的变量,该变量在循环时发生了变异。每个处理程序都引用该循环变量,因此在循环结束时,将设置为 ,并且所有处理程序都引用相同的值。iiiTotal

这可能是使用函数的 bind 方法的好时机。这样做的目的是返回函数的新版本,其中已建立上下文并预填充了参数。this

for (var i = 0; i < iTotal; i++) {
  el = iBlock.children[i];
  el.addEventListener('click', myHandler.bind(this, this, i));
}

每个函数都接收一个唯一的函数,其中预填充了前两个参数。el

评论

0赞 Jacob 9/26/2020
@Nikola将处理程序附加到父级的方法是更好的选择,但我正在发布,以便您可以了解您是否确实需要单个侦听器函数并且不想使用 IIFE。bind
0赞 Randy 9/26/2020
这看起来很有趣,我会投赞成票。但是,我将不得不更新我的问题,以表明当我说要支持较旧的浏览器时,如果可能的话,我希望它包括 IE-8,并且 .bind 在该浏览器中不起作用。我开始认为,在编辑我的问题时,我的“变通办法”可能是这些限制下的最佳解决方案。
0赞 Jacob 9/26/2020
啊,不,你将不得不与IIFE或活动代表团一起去。bind
0赞 Randy 9/28/2020 #4

在仔细考虑了提供的所有答案之后,我将坚持使用我发现并发布的解决方案,在我的 OP 的最终编辑中。 具体来说,当我添加事件时,它会为每个元素分配一个新属性......

someFunction(iBlock) {
    var el, iTotal = iBlock.children.length;
     for (var i= 0; i < iTotal; i++) {
             el=iBlock.children[i];
             el.onclick=function(event){myHandler(this)};
             el.myIndex=i;
             }
    }

在这里,由于注意到的问题,我什至不再尝试将“i”的值作为第二个参数发送到处理程序。然而,无论出于何种原因,当添加的元素“myIndex”被分配给“i”时,它会在分配时保留标识值。因此,当调用处理程序时,可以通过“this.myIndex”访问它。我不完全理解为什么这样做会导致“i”被值传递,而不是引用/实例本身,但它有效。

没有人提供任何理由不这样做,而且就添加的代码而言,它似乎是最不复杂的。我相信唯一的风险是选择一个与未来的“标准”属性相冲突的名称。也许这就是为什么我看到建议 tp 调用添加属性之类的东西,例如“data-myindex”。但无论哪种方式,它似乎都适用于我尝试过的所有 IE 平台,包括 Edge、Chrome 和 Firefox,直到最新的 Win-10 为 Windows-XP 分发的平台。它适用于 Mobil Safari 回到 IOS-7,以及我能够测试的所有 Android 设备。仍然对建议持开放态度,但有时,正如他们所说,“最简单的解决方案是最好的”

评论

1赞 Nikola Mitic 9/30/2020
我认为您误解了闭包和事件委托或使用 bind() 在某种程度上比您的解决方案更复杂。以下是您不应该使用上述解决方案的三个主要原因。1. 这并不简单 2.它将添加不必要的偶数处理程序 3.你不明白它是如何工作的(这就是为什么它不简单)你写的代码会被别人读取。闭包、事件委托或 bind() 都是常见的 javascript 技术,很容易被取之甚少。Ofc 的决定权在您手中。祝你有美好的一天!:)
0赞 Nikola Mitic 9/30/2020
在旁注中,“this”引用了附加事件处理程序的元素。我们所有的解决方案都基于这样一个事实,即您需要索引才能在事件处理程序中访问。但是您实际需要的是附加处理程序的元素。所以你的问题应该以不同的方式构建:)如果你关心它
0赞 Nikola Mitic 9/30/2020
E.Target 就是“这个”对你来说。因此,如果您使用事件委派,则可以避免 for 循环和不需要的事件侦听器。
0赞 Randy 9/30/2020
@NikolaMitic 好吧,我的标题确实表达了我的问题。是的,出于多种原因,我的事件处理程序需要“this”。一种是从配置阵列访问各种链接或函数。这些数组在构建元素时控制它们的属性,并指定元素在单击时将执行的操作。我所缺少的只是标识元素的从 0 开始的数字,作为数组的索引。对于你的观点,#1 我不同意。#2 我同意,但就我而言,项目数量很少,#3 我确实理解。您将根据索引在父子数组中的位置来查找索引。赞赏!
0赞 Randy 9/30/2020
@NikolaMitic请不要以为我没有欣赏或从你的解释中学到东西。我确实一直使用闭包,但是是的......活动授权是我从未考虑过的事情,将来肯定会考虑。