addEventListener использует для цикла и передачи значений
Я пытаюсь добавить прослушиватель событий к нескольким объектам, используя цикл for, но в итоге все слушатели нацеливаются на один и тот же объект -> последний.
Если я добавлю слушателей вручную, определив boxa и boxb для каждого экземпляра, это сработает. Я предполагаю, что это цикл for addEvent, который работает не так, как я надеялся. Может быть, я использую неправильный подход вообще.
Пример с использованием 4 триггера class="container" для контейнера 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>
5 ответов
Затворы!:D
Этот исправленный код работает так, как вы и предполагали:
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
}
Почему это исправить?
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
цикл вы добавляете обратные вызовы клика и назначаете boxa
, но его значение перезаписывается на следующей итерации. Когда происходит событие click, выполняется обратный вызов и значение boxa
всегда последний элемент в списке.
Использование замыкания (закрытие значений boxa
, boxb
и т. д.) вы привязываете значение к области действия обработчика кликов.
Инструменты анализа кода, такие как JSLint или JSHint, смогут перехватывать подозрительный код, подобный этому. Если вы пишете много кода, стоит потратить время на изучение того, как использовать эти инструменты. Некоторые IDE имеют их встроенные.
Вы можете использовать функцию привязки. Вам не нужно использовать замыкания. См. Ниже:
До:
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);
}
}
Вы столкнулись с проблемой объема / закрытия как function(){makeItHappen(boxa,boxb);}
Затем ссылки boxa и boxb всегда являются последним элементом (ами).
Чтобы решить проблему:
function makeItHappenDelegate(a, b) {
return function(){
makeItHappen(a, b)
}
}
// ...
elem[i].addEventListener("click", makeItHappenDelegate(boxa,boxb), false);
Это из-за замыканий.
Проверьте это: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
Пример кода и ваш код по сути одинаковы, это распространенная ошибка для тех, кто не знает "замыкание".
Проще говоря, когда вы создаете функцию-обработчик внутри addEvents()
, он не просто обращается к переменной i
от addEvents()
среда, но она также "помнит" i
,
И потому, что ваш обработчик "помнит" i
переменная i
не исчезнет после addEvents()
был выполнен.
Поэтому, когда вызывается обработчик, он будет использовать i
но переменная i
сейчас, после цикла, 3.
У меня тоже была эта проблема некоторое время назад. Я решил это, используя функцию "добавления" вне цикла, чтобы назначать события, и это работало отлично.
Ваш сценарий должен выглядеть следующим образом.
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";
}