【问题标题】:addEventListener using for loop and passing values [duplicate]addEventListener 使用 for 循环和传递值
【发布时间】:2013-11-04 08:28:27
【问题描述】:

我正在尝试使用 for 循环将事件侦听器添加到多个对象,但最终所有侦听器都针对同一对象 --> 最后一个。

如果我通过为每个实例定义 boxa 和 boxb 来手动添加侦听器,它就可以工作。我想这是 addEvent for-loop 没有按我希望的方式工作。也许我完全使用了错误的方法。

使用 class="container" 中的 4 个的示例 容器 4 上的触发器按预期方式工作。 在容器 4 上触发容器 1、2、3 触发事件,但前提是触发器已被激活。

// Function to run on click:
function makeItHappen(elem, elem2) {
  var el = document.getElementById(elem);
  el.style.backgroundColor = "red";
  var el2 = document.getElementById(elem2);
  el2.style.backgroundColor = "blue";
}

// Autoloading function to add the listeners:
var elem = document.getElementsByClassName("triggerClass");

for (var i = 0; i < elem.length; i += 2) {
  var k = i + 1;
  var boxa = elem[i].parentNode.id;
  var boxb = elem[k].parentNode.id;

  elem[i].addEventListener("click", function() {
    makeItHappen(boxa, boxb);
  }, false);
  elem[k].addEventListener("click", function() {
    makeItHappen(boxb, boxa);
  }, false);
}
<div class="container">
  <div class="one" id="box1">
    <p class="triggerClass">some text</p>
  </div>
  <div class="two" id="box2">
    <p class="triggerClass">some text</p>
  </div>
</div>

<div class="container">
  <div class="one" id="box3">
    <p class="triggerClass">some text</p>
  </div>
  <div class="two" id="box4">
    <p class="triggerClass">some text</p>
  </div>
</div>

【问题讨论】:

  • 它是因为闭包 :D ...但是你为什么不在父元素上添加事件监听器而不是添加到每个子元素中呢?

标签: javascript for-loop addeventlistener


【解决方案1】:

关闭! :D

此固定代码按您的预期工作:

// Function to run on click:
function makeItHappen(elem, elem2) {
    var el = document.getElementById(elem);
    el.style.backgroundColor = "red";
    var el2 = document.getElementById(elem2);
    el2.style.backgroundColor = "blue";
}

// Autoloading function to add the listeners:
var elem = document.getElementsByClassName("triggerClass");

for (var i = 0; i < elem.length; i += 2) {
    (function () {
        var k = i + 1;
        var boxa = elem[i].parentNode.id;
        var boxb = elem[k].parentNode.id;
        elem[i].addEventListener("click", function() { makeItHappen(boxa,boxb); }, false);
        elem[k].addEventListener("click", function() { makeItHappen(boxb,boxa); }, false);
    }()); // immediate invocation
}
<div class="container">
  <div class="one" id="box1">
    <p class="triggerClass">some text</p>
  </div>
  <div class="two" id="box2">
    <p class="triggerClass">some text</p>
  </div>
</div>

<div class="container">
  <div class="one" id="box3">
    <p class="triggerClass">some text</p>
  </div>
  <div class="two" id="box4">
    <p class="triggerClass">some text</p>
  </div>
</div>

为什么会修复它?

for(var i=0; i < elem.length; i+=2){
    var k = i + 1;
    var boxa = elem[i].parentNode.id;
    var boxb = elem[k].parentNode.id;

    elem[i].addEventListener("click", function(){makeItHappen(boxa,boxb);}, false);
    elem[k].addEventListener("click", function(){makeItHappen(boxb,boxa);}, false);
}

实际上是非严格的 JavaScript。是这样解释的:

var i, k, boxa, boxb;
for(i=0; i < elem.length; i+=2){
    k = i + 1;
    boxa = elem[i].parentNode.id;
    boxb = elem[k].parentNode.id;

    elem[i].addEventListener("click", function(){makeItHappen(boxa,boxb);}, false);
    elem[k].addEventListener("click", function(){makeItHappen(boxb,boxa);}, false);
}

由于variable hoistingvar 声明被移到了作用域的顶部。由于 JavaScript 没有块作用域(forifwhile 等),它们被移动到函数的顶部。 更新: 从 ES6 开始,您可以使用 let 来获取块作用域变量。

当您的代码运行时,会发生以下情况:在for 循环中,您添加点击回调并分配boxa,但其值在下一次迭代中被覆盖。当点击事件触发回调时,boxa 的值总是列表中的最后一个元素。

使用闭包(关闭boxaboxb 等的值)将值绑定到点击处理程序的范围。


JSLintJSHint 等代码分析工具将能够捕获此类可疑代码。如果您正在编写大量代码,那么花时间学习如何使用这些工具是值得的。有些 IDE 内置了它们。

【讨论】:

  • @musefan 他们是问题,因为变量提升。请看我的解释。
  • 应该}(i, k))})(i, k)
  • 实际上是无效的JavaScript。:如果无效,则不会解析或执行。 使用我向您展示的闭包,您确实得到了您所期望的行为:正确,但重要的一点是您在循环内执行一个函数,它创建一个新的范围。
  • @Wayne:无论哪种方式都可以。我个人觉得第一个更具可读性。
  • @Felix 土豆土豆,将措辞更改为 non-strict。 @Wayne 可以,但}()) 是我的偏好。
【解决方案2】:

你可以使用函数绑定。你不需要使用闭包。见下文:

之前

function addEvents(){
   var elem = document.getElementsByClassName("triggerClass");

   for(var i=0; i < elem.length; i+=2){
      var k = i + 1;
      var boxa = elem[i].parentNode.id;
      var boxb = elem[k].parentNode.id;

      elem[i].addEventListener("click", function(){makeItHappen(boxa,boxb);}, false);
      elem[k].addEventListener("click", function(){makeItHappen(boxb,boxa);}, false);
   }
}

之后

function addEvents(){
   var elem = document.getElementsByClassName("triggerClass");

   for(var i=0; i < elem.length; i+=2){
        var k = i + 1;
      var boxa = elem[i].parentNode.id;
      var boxb = elem[k].parentNode.id;
      elem[i].addEventListener("click", makeItHappen.bind(this, boxa, boxb), false);
      elem[k].addEventListener("click", makeItHappen.bind(this, boxa, boxb), false);
   }
}

【讨论】:

  • 我喜欢这个解决方案。非常清晰易读。
  • 我也喜欢这个。谢谢。
  • 不错的解决方案,易于实施和工作。您能否就其工作原理/原因提供更多见解? (我知道我可以直接搜索它,但最好直接在此处放置 1 或 2 段,以供将来最终来到这里的人使用。)
  • 我真的不知道这是如何工作的,但确实如此,而且男孩很干净!非常感谢!
【解决方案3】:

这是因为闭包。

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

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

简单地说,当您在addEvents() 中创建处理函数时,它不仅会从addEvents() 的环境中访问变量i,而且还会“记住”i

因为你的处理程序“记住”了i,所以变量iaddEvents()被执行后不会消失。

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

【讨论】:

    【解决方案4】:

    前段时间我也遇到过这个问题。 我通过在循环外使用“添加”函数来分配事件来解决它,并且效果很好。

    您的脚本应该如下所示。

    function addEvents(){
      var elem = document.getElementsByClassName("triggerClass");
      for(var i=0; i < elem.length; i+=2){
        var k = i + 1;
        var boxa = elem[i].parentNode.id;
        var boxb = elem[k].parentNode.id;
    
    //- edit ---------------------------|
        adds(boxa, boxb);
      }
    }
    //- adds function ----|
    function adds(obj1, obj2){
      obj1.addEventListener("click", function(){makeItHappen(obj1, obj2);}, false);
      obj2.addEventListener("click", function(){makeItHappen(obj1, obj2);}, false);
    }
    //- end edit -----------------------|
    
    function makeItHappen(elem, elem2){
      var el = document.getElementById(elem);
      el.style.transform = "flip it";
      var el2 = document.getElementById(elem2);
      el2.style.transform = "flip it";
    }

    【讨论】:

      【解决方案5】:

      您面临function(){makeItHappen(boxa,boxb);} boxaboxb 引用的范围/关闭问题,然后总是最后一个元素。

      解决问题:

      function makeItHappenDelegate(a, b) {
        return function(){
            makeItHappen(a, b)
        }
      }
      
      // ...
       elem[i].addEventListener("click", makeItHappenDelegate(boxa,boxb), false);
      

      【讨论】:

      • 这基本上是相同的解决方案。通过返回一个绑定到boxaboxb 的实际值的函数,您可以在makeItHappenDelegate 中创建一个闭包。
      • 是的,但是更好,因为我们不会在每个循环上创建自调用函数。是的,我们创建了委托,但这是最小的开销。
      • 在这种情况下,我怀疑(极少的)性能开销超过了可读性的好处。如果您有很长的列表,例如 100.000+,您可能会看到性能优势,但低于此我真的不会担心。可读性更为重要。
      • 嗯,委托是更好的方法,即使在可读性和可维护性方面也是如此。 SOLID
      • 我同意,我发现这更具可读性和可维护性。
      猜你喜欢
      • 2021-12-03
      • 2017-12-12
      • 1970-01-01
      • 1970-01-01
      • 2017-11-24
      • 2018-03-13
      • 2019-01-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多