【问题标题】:Javascript multiple dynamic addEventListener created in for loop - passing parameters not working在for循环中创建的Javascript多个动态addEventListener - 传递参数不起作用
【发布时间】:2013-01-05 23:56:51
【问题描述】:

我想使用事件侦听器来防止带有 onclick 函数的 div 内的 div 上的事件冒泡。这行得通,按照我的意图传递参数:

<div onclick="doMouseClick(0, 'Dog', 'Cat');" id="button_id_0"></div>
<div onclick="doMouseClick(1, 'Dog', 'Cat');" id="button_id_1"></div>
<div onclick="doMouseClick(2, 'Dog', 'Cat');" id="button_id_2"></div>

<script>
function doMouseClick(peram1, peram2, peram3){
    alert("doMouseClick() called AND peram1 = "+peram1+" AND peram2 = "+peram2+" AND peram3 = "+peram3);
}
</script>

但是,我尝试使用以下方法在循环中创建多个事件侦听器:

<div id="button_id_0"></div>
<div id="button_id_1"></div>
<div id="button_id_2"></div>

<script>
function doMouseClick(peram1, peram2, peram3){
    alert("doMouseClick() called AND peram1 = "+peram1+" AND peram2 = "+peram2+" AND peram3 = "+peram3);
}

var names = ['button_id_0', 'button_id_1', 'button_id_2'];

    for (var i=0; i<names.length; i++){

        document.getElementById(names[i]).addEventListener("click", function(){
        doMouseClick(i, "Dog", "Cat");

    },false);

}

</script>

它正确地将点击函数分配给每个 div,但每个 div 的第一个参数 peram13。我期待 3 个不同的事件处理程序都为 peram1 传递不同的 i 值。

为什么会这样?事件处理程序不都是分开的吗?

【问题讨论】:

    标签: javascript events parameter-passing addeventlistener


    【解决方案1】:

    问题是闭包,因为 JS 没有块作用域(只有函数作用域)i 不是你想的那样,因为事件函数会创建另一个作用域,所以当你使用 i 时,它已经是最新的值了for 循环。您需要保留i 的值。

    使用 IIFE:

    for (var i=0; i<names.length; i++) {
      (function(i) {
        // use i here
      }(i));
    }
    

    使用forEach

    names.forEach(function( v,i ) {
      // i can be used anywhere in this scope
    });
    

    【讨论】:

    • 谢谢。我想知道为什么 i 超出范围并返回 undefined 元素。
    【解决方案2】:

    2022 年编辑

    由于 9 年后有人仍在阅读并支持此答案,因此这是现代的做法:

    for (const [i, name] of names.entries()) {
        document.getElementById(name).addEventListener("click", () => doMouseClick(i, "Dog", "Cat"), false);
    }
    

    使用constlet 定义变量为它们提供块级范围,并且传递给处理函数的i 的值对于循环的每次迭代都是不同的,正如预期的那样。

    旧方法仍然有效,但不再需要。

    2013 年答案

    正如已经指出的那样,问题与闭包和变量范围有关。确保传递正确值的一种方法是编写另一个返回所需函数的函数,将变量保存在正确的范围内。 jsfiddle

    var names = ['button_id_0', 'button_id_1', 'button_id_2'];
    
    function getClickFunction(a, b, c) {
      return function () {
        doMouseClick(a, b, c)
      }
    }
    for (var i = 0; i < names.length; i++) {
      document.getElementById(names[i]).addEventListener("click", getClickFunction(i, "Dog", "Cat"), false);
    }
    

    为了说明一种方法,你可以用一个对象来代替:

    var names = ['button_id_0', 'button_id_1', 'button_id_2'];
    
    function Button(id, number) {
      var self = this;
      this.number = number;
      this.element = document.getElementById(id);
      this.click = function() {
        alert('My number is ' + self.number);
      }
      this.element.addEventListener('click', this.click, false);
    }
    for (var i = 0; i < names.length; i++) {
      new Button(names[i], i);
    }
    

    或略有不同:

    function Button(id, number) {
      var element = document.getElementById(id);
      function click() {
        alert('My number is ' + number);
      }
      element.addEventListener('click', click, false);
    }
    for (var i = 0; i < names.length; i++) {
      new Button(names[i], i);
    }
    

    【讨论】:

    • 这很有帮助,谢谢。对我来说有点先进,因为我刚开始在 javascript 中使用事件侦听器 - 我将进一步研究。
    【解决方案3】:

    这是因为闭包。

    看看这个:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures#Creating_closures_in_loops_A_common_mistake

    示例代码和你的代码本质上是一样的,对于不知道“闭包”的人来说这是一个常见的错误。

    简单地说,当你创建一个处理函数时,它不仅从外部环境访问变量i,而且还“记住”了i

    因此,当调用处理程序时,它将使用 i,但变量 i 现在在 for 循环之后为 2。

    【讨论】:

    • 鼓励链接到外部资源,但请在链接周围添加上下文,以便您的其他用户了解它是什么以及为什么存在。始终引用重要链接中最相关的部分,以防目标站点无法访问或永久离线。来自How to Answer
    • @MoralesBatovski 感谢您的提醒。我刚刚编辑了我的帖子。
    【解决方案4】:

    我自己已经为这个问题苦苦挣扎了几个小时,现在我刚刚设法解决了它。这是我的解决方案,使用 function constructor:

    function doMouseClickConstructor(peram1, peram2, peram3){
        return new Function('alert("doMouseClick() called AND peram1 = ' + peram1 + ' AND peram2 = ' + peram2 + ' AND peram3 = ' + peram3 + ');');
    }
    
    for (var i=0; i<names.length; i++){
        document.getElementById(names[i]).addEventListener("click", doMouseClickConstructor(i,"dog","cat"));
    };
    

    注意:我还没有实际测试过这段代码。然而,我已经测试了这个codepen,它做了所有重要的事情,所以如果上面的代码不起作用,我可能只是犯了一些拼写错误。这个概念应该仍然有效。

    编码愉快!

    【讨论】:

      【解决方案5】:

      在 javascript 中一切都是全局的。它在循环后调用变量i,该变量设置为3...如果在循环后将i 设置为1000,那么您会看到每个方法调用为i 生成1000。

      如果你想保持状态,那么你应该使用对象。让对象具有分配给 click 方法的回调方法。

      您提到这样做是为了事件冒泡......为了停止事件冒泡,您真的不需要这样做,因为它是内置在语言中的。如果您确实想防止事件冒泡,那么您应该使用传递给回调的event 对象的stopPropagation() 方法。

      function doStuff(event) {
          //Do things
          //stop bubbling
          event.stopPropagation();
      }
      

      【讨论】:

      • 感谢您帮助我处理 stopPropogation() - 它解决了我问题的另一部分。事件监听器给我留下了与您建议的相同的冒泡问题——我试图解决这个问题——但是这里的这个答案也解决了我的冒泡问题。再次感谢 :) 伙计!这个 Stackoverflow 是一群乐于助人的人!
      • 关于“如果你在循环之后将 i 设置为 1000,那么你会看到每个方法调用为 i 产生 1000”的 cmets 也完全准确:)
      猜你喜欢
      • 1970-01-01
      • 2018-11-16
      • 2013-11-04
      • 2020-05-24
      • 2020-04-20
      • 1970-01-01
      • 1970-01-01
      • 2014-10-19
      • 2014-04-17
      相关资源
      最近更新 更多