Javascriptの悪名高いループの問題? [Javascript] [Closures]

このエントリーをはてなブックマークに追加

質問:

次のコードスニペットがあるんだけど、

function addLinks () {
for (var i=0, link; i<5; i++) {
link = document.createElement("a");
link.innerHTML = "Link " + i;
link.onclick = function () {
alert(i);
};
document.body.appendChild(link);
}
}

上記のコードは、5つのリンクを生成して、現在のリンクIDを表示するために各リンクをアラートイベントでバインドするためのコード。
でも、動作しないんだ。クリックすると生成されたリンクはすべて「リンク5」になる。

でも、次のコードスニペットは僕の期待通りに機能する。

function addLinks () {
for (var i=0, link; i<5; i++) {
link = document.createElement("a");
link.innerHTML = "Link " + i;
link.onclick = function (num) {
return function () {
alert(num);
};
}(i);
document.body.appendChild(link);
}
}

上記の2つのスニペットはここから拝借した。著者の解説にあるように、closureは魔法のように見える。

でも、どのように動作し、どのようにクロージャが動作するかは、僕には理解不能。なぜ最初のものは動作しないの?誰かこの魔法の詳細な説明ってできる?

よろしくお願いします。。

By Zhu Tao | 質問日時: 2009年9月20日 13:21



回答1:

最初の例の説明として、僕の以前の回答を引用してみる:

JavaScriptのスコープはブロックレベルではなく、関数レベル。closureを作ることは、囲むスコープが、囲まれる関数のレキシカルな環境に追加されることを意味する。
ループが終了すると、関数レベル変数iの値は5になり、それは内部の関数が「見ている」ものだ。

2番目の例では、反復ステップごとに、外部関数リテラルがそれ自身の有効範囲とローカル変数numを持つ新しい関数オブジェクトを評価して、その値は現在の値「i」に設定される。numは決して変更されないので、クロージャの生存期間にわたって定数として保たれる:関数オブジェクトが独立しているので、次の反復ステップでは古い値を上書きしない。

このアプローチは、リンクごとに2つの新しい関数オブジェクトを作成する必要があるので非効率だってことに注意。これは基本的に不要、なぜかというと情報ストレージとしてDOMノードを使用するとそれらは簡単に共有できるから。

function linkListener() {
alert(this.i);
}
function addLinks () {
for(var i = 0; i < 5; ++i) {
var link = document.createElement('a');
link.appendChild(document.createTextNode('Link ' + i));
link.i = i;
link.onclick = linkListener;
document.body.appendChild(link);
}
}

By Christoph | 回答日時: 2009年9月20日 13:43



回答2:

ここで簡単な説明を書いてみる:

ページにはそれぞれID野着いたdiv1、div2、div3、div4、div5の5つのdivがある。
jQueryではこうできる…

for (var i=1; i<=5; i++) {
$("#div" + i).click ( function() { alert ($(this).index()) } )
}

で、問題に対処していこう(ゆっくりと構築していく)…

ステップ1

for (var i=1; i<=5; i++) {
$("#div" + i).click (
// TODO: クリックイベントを処理するための書き込み関数
)
}

ステップ2

for (var i=1; i<=5; i++) {
$("#div" + i).click (
function(num) {
            //関数の値は、関数が呼び出されたときに設定されてる!
            //これを理解すればもう安心(僕は2年かかった)!
            // clickイベントはハンドラとしての関数を期待しているので、それを返すr
return function() { alert (num) }
}(i) // ここで関数を呼び出し、iの中の値を渡す
)
}

簡単に理解できる別の方法

もしこれがわかりにくければ、これなら簡単にわかるかも。やってることは同じ…

for (var i=1; i<=5; i++) {
function clickHandler(num) {
$("#div" + i).click (
function() { alert (num) }
)
}
clickHandler(i);
}

これは、関数変数の値は関数が呼び出されたときに設定される、ってことを覚えていれば分かりやすいはず(でも、これは上とまったく同じ思考プロセスを使ってる)。

By Daniel Lewis | 回答日時: 2012年1月29日 16:27



回答3:

他の人は何が起こっているのかを説明したけど、ここでは別の解決法を提供してみる。

function addLinks () {
for (var i = 0, link; i < 5; i++) {
link = document.createElement("a");
link.innerHTML = "Link " + i;
with ({ n: i }) {
link.onclick = function() {
alert(n);
};
}
document.body.appendChild(link);
}
}

基本的には、貧乏人のためのletバインディング。

By nlogax | 回答日時: 2009年9月26日 21:38



Source: Javascript infamous Loop issue? [duplicate]

共有 コメント