Forループを使用してイベントリスナーを複数のオブジェクトに追加しようとしていますが、すべてのリスナーが同じオブジェクトをターゲットにしています->最後のリスナー。
すべてのインスタンスにboxaとboxbを定義して手動でリスナーを追加すると、機能します。 addEvent for-loopが私が望んでいた方法で動作していないと思います。多分私は間違ったアプローチを完全に使用します。
コンテナ4でclass = "container" Triggerを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>
クロージャー! :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);
}
variable hoisting のため、var
宣言はスコープの最上部に移動します。 JavaScriptにはブロックスコープ(for
、if
、while
など)がないため、関数の最上部に移動します。 Update:ES6以降、 let
を使用してブロックスコープ変数を取得できます。
コードを実行すると、for
ループでクリックコールバックを追加し、boxa
を割り当てますが、その値は次の反復で上書きされます。クリックイベントが発生すると、コールバックが実行され、boxa
の値はalwaysリストの最後の要素になります。
クロージャー(boxa
、boxb
などの値を閉じる)を使用して、値をクリックハンドラーのスコープにバインドします。
JSLint または JSHint などのコード分析ツールは、このような疑わしいコードをキャッチできます。大量のコードを書いている場合、これらのツールの使用方法を学ぶのに時間をかける価値があります。一部のIDEにはそれらが組み込まれています。
function(){makeItHappen(boxa,boxb);}
boxaおよびboxb参照としてスコープ/クロージャの問題に直面すると、常に最後の1つの要素が参照されます。
問題を解決するには:
function makeItHappenDelegate(a, b) {
return function(){
makeItHappen(a, b)
}
}
// ...
elem[i].addEventListener("click", makeItHappenDelegate(boxa,boxb), false);
Function Bindingを使用できます。クロージャを使用する必要はありません。以下を参照してください。
前:
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);
}
}
私も少し前にこの問題を抱えていました。ループの外側で「adds」関数を使用してイベントを割り当てることで解決し、完全に機能しました。
スクリプトは次のようになります。
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";
}
それは閉鎖のためです。
これをチェックしてください: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures#Creating_closures_in_loops_A_common_mistake
サンプルコードとコードは基本的に同じです。「クロージャ」を知らない人にとってはよくある間違いです。
簡単に言うと、addEvents()
内にハンドラー関数を作成すると、addEvents()
の環境から変数i
にアクセスするだけでなく、i
を「記憶」します。
また、ハンドラはi
を「記憶」しているため、_addEvents()
が実行された後、変数i
は消えません。
そのため、ハンドラーが呼び出されると、i
が使用されますが、変数i
はforループの後、3になります。