メモ用の自作wikiを作り*1、やがては自作簡易CMSに発展させようと常々考えておりまして。 そこで少しづつ作り始めたのですが、ファイルのmultipleなアップロードで「これはどうやって進捗状況の表示をするのだろう?」と調べたんですが、そこで良くわからない表記が出てきました。 一体クロージャというのは便利なのか、何なのか…
ともかくそれをやらないと進捗状況ループが1ファイルで終わってしまい、他のファイルは進捗しないというのです。
それが"infamous loop issue"だそうなのですが、一応メモって置きます。
元ネタ:
closures - Javascript infamous Loop issue? - Stack Overflow
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); } }
これの期待されている動作は、アンカーリンクをjavascriptで動的に作って、それをクリックするとその作成時のi、すなわちLink0なら0、Link1なら1をalertダイアログで表示するというものなんですが、こういう風に作るとどのリンクをクリックしても「5」を表示してくれます。
どうもこの仕組みだと、クロージャはループ変数のiをそのまま保持するらしく、ループを抜けた時の数値である5を表示してくれるというわけですね。
つまりスコープを変えてクロージャに渡すループ変数のその時点のコピーを得る必要があると。
方法1:無名関数の中からクロージャをreturnする
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); } }
直後に(i)で呼び出してしまうのが味噌ですね。
ここまでが、上記stackoverflowの記事の意訳みたいなものなわけです。
方法2:スコープ自体を新規に作成してしまう
function addLinks () { for (var i=0, link; i<5; i++) { link = document.createElement("a"); link.innerHTML = "Link " + i; (function(num){ link.onclick=function() { alert(num); }; }(i)); document.body.appendChild(link); } }
無名関数を括弧でくくって、その中でonclickを設定するという手法です。 iはその時点の無名関数の引数としてコピーされるので、期待通りの動作になると。
これまた、(i)で宣言後即座に呼ぶのが味噌ですね。
この方法2の表記が一体何を意味しているのか?全く分からなかったというか、実は方法1もよくわかっておらずコピペで済ませていたのですが、そういう意味があったとは。勉強になりました。
*1:何故かというとmoinmoinが若干使いづらい気がしてきたので…