Htmlタグ内の標準onClick
をリスナーにリファクタリングすると、私のコードで問題が発生します:
var td;
for (var t=1;t<8;t++){
td = document.getElementById('td'+t);
if (typeof window.addEventListener==='function'){
td.addEventListener('click',function(){
console.log(td);
})}
}
td
要素をクリックすると、ループの最後のインデックスでtd
がクリックされたと想定されます。 7
、eventListeners
のように見えますが、このループの最後の要素のみに入力されています。
ループの初期化は正しいようです。
なぜそうなったのですか?
これが ライブコード です
イベントリスナーの割り当てをクロージャーでラップする必要があります。
var td;
for (var t = 1; t < 8; t++){
td = document.getElementById('td'+t);
if (typeof window.addEventListener === 'function'){
(function (_td) {
td.addEventListener('click', function(){
console.log(_td);
});
})(td);
}
}
変数td
はイベントハンドラーの外部で定義されているため、セルをクリックすると、最後に設定された値が記録されます。
より技術的には、各イベントハンドラー関数はクロージャー、つまり外部スコープから変数を参照する関数です。
この種の問題の一般的な解決策は、ラッパー関数からイベントハンドラーを返し、引数として「修正」する変数を渡すことです。
td.addEventListener('click', function(wrapTD) {
return function() { console.log(wrapTD); }
}(td));
引数は、呼び出されたラッパー関数のスコープにバインドされます。
this
を使用しますただし、さらに簡単なオプションがあります。イベントハンドラーでは、this
はハンドラーが定義されている要素に設定されますなので、this
の代わりにtd
を使用できます。
td.addEventListener('click', function() {
console.log(this);
});
最後に、for
ループを完全に取り除くことができますで、テーブル全体に単一のイベントハンドラーを設定します。
var table = document.getElementById('my-table');
table.addEventListener('click', function(e) {
if (e.target.nodeName.toUpperCase() !== "TD") return;
var td = e.target;
console.log(td);
});
これは、複数のイベントハンドラーを1つだけに置き換えるため、大規模なテーブルにははるかに優れたソリューションです。テキストを別の要素でラップする場合、ターゲット要素がtd
の子孫であるかどうかを確認するためにこれを適応させる必要があることに注意してください。
したがって、ここで起こっているのは、イベントリスナー関数のスコープに変数「td」を保持しているということです。 'td'のインスタンスは1つしかなく、forループが繰り返されるたびに更新されます。したがって、forループが終了すると、tdの値は要素「#td7」に設定され、イベントハンドラーは単にtdの現在の値をログに記録します。
上記の例では、単純に「this」をログに記録できます。
var td;
for (var t=1;t<8;t++){
td = document.getElementById('td'+t);
if (typeof window.addEventListener==='function'){
td.addEventListener('click',function(){
console.log(this);
});
}
}
「this」が要素に設定されているため、イベントハンドラーの実行のためにイベントがバインドされました。
Forループ内からクロージャーを作成するときにイテレーターを保持することについて、もっと多くの答えを探していると思います。これを行うには、forループの外で関数を定義します。
for (var t=1;t<8;t++){
bind_event(t);
}
function bind_event(t) {
var td = document.getElementById('td'+t);
if (typeof window.addEventListener==='function'){
td.addEventListener('click',function(){
console.log(td);
});
}
}
このように、bind_eventが実行されるたびに「td」という名前の変数のインスタンスが作成され、そのインスタンスはイベントリスナー関数のクロージャー内に保持されます。 bind_eventの「t」も新しい変数であることは注目に値します。
私が理解しているように、それは閉鎖のために発生します... FORステートメントのスコープ内でイベントハンドラーを割り当てます。クリックが発生すると、TD変数がFORのスコープ内にある場合、最後のバージョンが取得され、ログに書き込まれます。
以下は期待どおりに機能するはずです。
for (var t=1;t<8;t++){
var td;
td = document.getElementById('td'+t);
if (typeof window.addEventListener==='function'){
td.addEventListener('click',function(){
console.log(this);
})}
}