AddEventListener, использующий для цикла и пропущенных значений

Я пытаюсь добавить слушатель событий к нескольким объектам, используя цикл for, но в итоге все слушатели нацеливаются на один и тот же объект → последний.

Если я добавлю слушателей вручную, определив boxa и boxb для каждого экземпляра, это сработает. Я думаю, это цикл for addEvent, который работает не так, как я надеялся. Может быть, я вообще использую неправильный подход.

Пример использования 4 класса = "контейнер" Триггер на контейнере 4 работает так, как он должен. Триггер для контейнера 1,2,3 триггерного события в контейнере 4, но только если триггер уже активирован.

// 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>

+74
источник поделиться
5 ответов

Затворы!: 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);
}

Из-за поднятия переменной объявления var перемещаются в верхнюю часть области. Поскольку JavaScript не имеет области видимости блока (for, if, while и т.д.), Они перемещаются в верхнюю часть функции. Обновление: начиная с ES6 вы можете использовать let для получения переменных области блока.

Когда ваш код выполняется, происходит следующее: в цикле for вы добавляете обратные вызовы click и назначаете boxa, но его значение перезаписывается на следующей итерации. Когда происходит событие click, выполняется обратный вызов, и значение boxa всегда является последним элементом в списке.

Используя замыкание (закрытие значений boxa, boxb и т.д.), Вы привязываете значение к области действия обработчика щелчков.


Инструменты анализа кода, такие как JSLint или JSHint, смогут перехватывать подозрительный код, подобный этому. Если вы пишете много кода, стоит потратить время на то, чтобы научиться пользоваться этими инструментами. В некоторых IDE они встроены.

+115
источник

Вы сталкиваетесь с проблемой scope/close как function(){makeItHappen(boxa,boxb);} ссылки boxa и boxb, а затем всегда один из следующих элементов.

Чтобы решить проблему:

function makeItHappenDelegate(a, b) {
  return function(){
      makeItHappen(a, b)
  }
}

// ...
 elem[i].addEventListener("click", makeItHappenDelegate(boxa,boxb), false);
+12
источник
другие ответы

Связанные вопросы


Похожие вопросы

Вы можете использовать функцию Binding.You не нужно использовать закрытие. Смотрите ниже:

До

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);
   }
}
+6
источник

У меня также была эта проблема некоторое время назад. Я решил это, используя функцию "добавляет" вне цикла, чтобы назначить события, и он отлично работал.

Ваш script должен выглядеть.

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";
}
+1
источник

Это из-за закрытия.

Проверьте это: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures#Creating_closures_in_loops_A_common_mistake

Код примера и ваш код по существу одинаковы, это распространенная ошибка для тех, кто не знает "закрытия".

Проще говоря, когда вы создаете функцию обработчика внутри addEvents(), она не просто обращается к переменной i из среды addEvents(), но также "запоминает" i.

И поскольку ваш обработчик "запоминает" i, переменная i не исчезнет после выполнения addEvents().

Итак, когда обработчик вызывается, он будет использовать i, но переменная i теперь, после цикла for, 3.

+1
источник

Посмотрите другие вопросы по меткам или Задайте вопрос