【问题标题】:How can I pass the current value of a variable to an event handler (Javascript)如何将变量的当前值传递给事件处理程序(Javascript)
【发布时间】:2021-01-12 02:05:24
【问题描述】:

对此有点尴尬,但假设我在一个名为 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);
    }

有点令人惊讶的是,无论我单击哪个子元素,此示例中的处理程序总是显示小于子元素总数的那个。但我知道,total - 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。

我看过关于这个主题的类似帖子,例如Extract a number from a string (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 属性发生冲突。

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

【问题讨论】:

  • n 未重新声明,并且您的事件处理程序基本上指向相同的 n 变量,因此它将获得在 r 执行时分配的最后一个值。试试这个:el.onclick=(function(localn){return function(event){myHandler(this, localn)};})(n);
  • 在我看来,您需要使用闭包来捕获该变量。你试过吗?
  • 在函数中,你声明了一个名为el的变量,你没有给它分配任何值,但是你使用el.onclick=....你不应该将每个iBlock子分配给el变量第一?
  • el 也未定义
  • el = iBlock.children[i] 或什么?

标签: javascript scope pass-by-reference pass-by-value


【解决方案1】:

在仔细考虑了所有提供的答案后,我将坚持使用我在 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 call add on sttributes 类似“data-myindex”。但无论哪种方式,它似乎都适用于我尝试回到 IE8 的所有 IE 平台,Edge、Chrome 和 Firefox 也适用于最后一个为 Windows-XP 分发到最新 Win-10 的平台。它适用于 Mobil Safari 回到 IOS-7,以及我能够测试的所有 android 设备。仍然愿意接受建议,但有时,正如他们所说,“最简单的解决方案就是最好的”

【讨论】:

  • 我认为您误解了闭包和事件委托或使用 bind() 在某种程度上比您的解决方案更复杂。以下是您不应采用上述解决方案的三个主要原因。 1.它不简单 2.它会添加不必要的偶数处理程序 3.你不明白它是如何工作的(这就是它不简单的原因) 你写的代码会被别人阅读。闭包、事件委托或 bind() 都是常见的 javascript 技术,很容易不受欢迎。 Ofc 的决定是你的。玩的开心! :)
  • 在旁注中,“this”是指附加事件处理程序的元素。我们所有的解决方案都基于您需要在事件处理程序中访问索引这一事实。但是您实际需要的是附加处理程序的元素。所以你的问题应该有不同的框架:)如果你关心它
  • e.target 对你来说就是“这个”。因此,如果您使用事件委托,您可以避免 for 循环和不需要的事件侦听器。
  • @NikolaMitic 好吧,我的标题确实表达了我的问题。是的,我的事件处理程序需要“this”有几个原因。一种是从配置数组访问各种链接或功能。这些数组在构建时控制我的元素的属性,并指定单击时元素将执行的操作。我所缺少的只是标识元素的基于 0 的数字,作为数组的索引。对于您的观点,#1 我不同意。 #2我同意,但就我而言,项目数量很少,#3我明白。您正在根据其在父子数组中的位置查找索引。赞赏!
  • @NikolaMitic 请不要想一分钟我不欣赏或从你的解释中学习。我确实一直使用闭包,但是是的......事件委托是我从未考虑过的事情,而且将来肯定会。
【解决方案2】:

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

More about closures

More about 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。

More about event delegation

  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>

【讨论】:

  • 一个问题是,正如我所提到的,我致力于支持旧版浏览器,并且我应该澄清一下,我希望尽可能包含 IE-8。 IFFE 似乎开始在 IE9 中部分工作。而且,我自己的理解,我读过,IEFE 函数在声明时被调用。那些人一被宣布就被“称为”吗?如果是这样,在我实际单击元素之前,我希望(例如)何时运行“onclick”处理程序?
  • 我不知道 IIFE 不能在 IE-8 中工作,IIFE 是 JS 语言的一部分,所以我认为它应该可以工作。你是对的,IIFE 一经宣布就会运行,这没有错。 onClick handler 是一个 handler 或者更好的回调函数,它只会在浏览器 dispatch click 事件时,所以当用户点击它时。
  • 有什么阻止你不使用 for 循环和不附加所有这些事件侦听器,而是像我在第二个示例中向你展示的那样使用 e.target 吗?
  • 我确实在 MDM 网络文档图表上找到了 IE-9+ 支持,但是为了回答您的问题,这是我正在翻新的一个有点“旧”的网站上导航菜单系统的一部分.因为导航系统可能会经常更新,所以我创建它的方式是将数组中的模板和项目转换为适当的图标、文本链接和操作。可以想象,与更新硬代码相比,添加到数组更容易且更容易出错。我知道没有办法在不丢失一些浏览器的情况下更新整个旧网站,但导航系统必须工作。谢谢
  • 我明白了,那么 IIFE(我发布的第一个示例)将为您工作,因为它们不是 web api 的一部分,而是语言本身。我什至相信事件委托(第二个例子)可以在 IE-8 上工作,但我不确定
【解决方案3】:

如 cmets 中所述,问题在于您的内联点击处理函数引用了闭包中的变量 i,该变量在循环时会发生突变。每个处理程序都引用该循环变量,因此在循环结束时,i 被设置为 iTotal,并且所有处理程序都引用相同的值。

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

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

每个el 都会收到一个独特的函数,其中前两个参数已预先填充。

【讨论】:

  • @Nikola 将处理程序附加到父级的方法是更好的选择,但我发帖是为了让您可以看到有关 bind 的信息,如果您确实需要单独的侦听器函数并且不需要想使用 IIFE。
  • 这看起来很有趣,我会投赞成票。但是,我将不得不更新我的问题,以表明当我说我想支持旧版浏览器时,如果可能的话,我希望它包含 IE-8,并且 .bind 不会在该浏览器中工作。我开始认为我的“变通办法”,在我的问题的编辑中,可能是这些限制下的最佳解决方案。
  • 啊,没有bind,您将不得不使用 IIFE 或事件委托。
【解决方案4】:

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

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

【讨论】:

  • 对不起...这是在评论中指出的,我已经对其进行了编辑以显示我如何将“el”分配给当前索引的孩子。但无论是我的编辑还是你的编辑都不能解决在处理程序中,'i' 将始终显示 total-1 的事实
  • @Randy 您需要创建一个闭包来捕获该迭代器值,请查看我的答案。如果你可以只使用 let,你就不需要这个。因为 let 没有被提升,所以它是在词法上被挖掘出来的,所以这不是问题
  • @NikolaMitic 是的,当我第一次意识到“让”在 IE-8 中不起作用时,我非常沮丧。但无论如何,现在已经做了一些研究,我发现我发现的向元素添加属性的解决方案比我想象的要广泛。它在事件处理程序中作为“this”的属性可用,即使它是通过将其分配给“i”变量而在循环中创建的,该属性也反映了“i”在添加时的值,这是完美的。我看到的唯一警告是我需要避免添加可能与未来标准属性冲突的属性名称。
猜你喜欢
  • 2018-08-03
  • 1970-01-01
  • 1970-01-01
  • 2023-03-27
  • 2011-05-02
  • 1970-01-01
  • 1970-01-01
  • 2014-08-29
  • 1970-01-01
相关资源
最近更新 更多