モブ沢工房

プログラミングとかLinux関連(特にOSSのグラフィックツール関連)とかレトロゲームとか3Dプリンタやら日曜大工等、色々。

javascriptのinfamous loop issueについて学んだ

メモ用の自作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が若干使いづらい気がしてきたので…