web-dev-qa-db-ja.com

forループを使用して値を渡すaddEventListener

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>
73
user2919172

クロージャー! :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にはブロックスコープ(forifwhileなど)がないため、関数の最上部に移動します。 Update:ES6以降、 let を使用してブロックスコープ変数を取得できます。

コードを実行すると、forループでクリックコールバックを追加し、boxaを割り当てますが、その値は次の反復で上書きされます。クリックイベントが発生すると、コールバックが実行され、boxaの値はalwaysリストの最後の要素になります。

クロージャー(boxaboxbなどの値を閉じる)を使用して、値をクリックハンドラーのスコープにバインドします。


JSLint または JSHint などのコード分析ツールは、このような疑わしいコードをキャッチできます。大量のコードを書いている場合、これらのツールの使用方法を学ぶのに時間をかける価値があります。一部のIDEにはそれらが組み込まれています。

113
Halcyon

function(){makeItHappen(boxa,boxb);}boxaおよびboxb参照としてスコープ/クロージャの問題に直面すると、常に最後の1つの要素が参照されます。

問題を解決するには:

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

// ...
 elem[i].addEventListener("click", makeItHappenDelegate(boxa,boxb), false);
12
tenbits

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);
   }
}
6
msantos

私も少し前にこの問題を抱えていました。ループの外側で「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";
}
1
blue3d

それは閉鎖のためです。

これをチェックしてください: 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になります。

1
olindk