ログインしてさらにmixiを楽しもう

コメントを投稿して情報交換!
更新通知を受け取って、最新情報をゲット!

JavaScriptコミュのfor文内での変数の挙動に関して。

  • mixiチェック
  • このエントリーをはてなブックマークに追加
for文中の変数の扱いや、スコープの実体が
時々直感的でない言語仕様に思えて、で、追求してるうちに
一層わけが分からなくなってきたりします。
もしよかったら、ご意見お聞かせ下さい。

例えば、次のようなコード、

<html>
<head>
<script type="text/javascript">
var SIZE = 10;
window.onload = function(){
for(var i=0; i<SIZE; i++){
var el = document.createElement("span");
el.appendChild(document.createTextNode(i))
el.onclick = function(){
alert(i);
}
document.body.appendChild(el);
document.body.appendChild(document.createElement("br"));
}
}
</script>
</head>
</html>

これを実行すると、すべてのspanのonclickで "10" がalertされます。
これは、clickした時点での i がalertされてるってことで、
「まぁ、そんなもんかなぁ」と、やや納得。
本当は、それぞれ表示されている文字と同じ値をalertして欲しいんですけどね。

一旦どこかのオブジェクトに値をセットしておけば良かろうってことで
var el = document.createElement("span");
el.__index = i;
el.appendChild(document.createTextNode(i))
el.onclick = function(){
alert(this.__index);
}
としてやると、とりあえず問題なく期待に応えてくれました。
ただ、それぞれのelに余計なプロパティがセットされて、気持ち悪い…。

ということで、新しい変数を用意して、
var index = i; // i + "" とかにしても同様。
var el = document.createElement("span");
el.appendChild(document.createTextNode(index))
el.onclick = function(){
alert(index);
}
としてやると、今度はすべて "9" がalertされます。
loopの度に変数が生成されているはずなのに…!?

納得いかないですけど、結果からすると、
loop毎に違うスコープになっているのではなく、
var i = 0 とかと同様、for文全体で1つのスコープになってる感じですよね…。
var el も、forの中では上書きされてるのでしょうね。
appendChildしてるから、document上では生き残ってるけど。

というわけで、結果からただ推測してるだけですが、
同様の、期待と違う動作の例 や、エレガントな解決策
など、もしよろしければなんでもお聞かせ下さい。
長くてスミマセン。よろしくお願いいたします。

コメント(8)

こう書きますー。

el.onclick = function(){
alert(i);
}

el.onclick = function (tmp) {
    return function(){ alert(tmp); }
}(i);

最初のでは i は一応クロージャとして利いてるんですが、1とか2とかいう値が参照されてるわけじゃなく、i というオブジェクトそのものが参照されてるので、i が 10 になったら 10 を alert しちゃう、って解釈してます。
こんな書き方あるなんて知らなかった(;´Д`)勉強になりました
おお〜! 7さん、さすがっ!
そっか、クロージャ使って「実行結果として」関数を生成すれば
変化した後の i を引きずらなくて済みますね〜

ありがとうございました!
今後、クロージャは乱用しそうですw

にゃんこ【・ω・】さんも、考えていただけてありがとうございます。

JavaScriptだと、数値はprimitive型として扱われないから、
こういうとき、ちょっと違和感がありますよね〜

--------------------

さて、for内のスコープは…、と思って、
さらに次のようなコードを試してみました。
すべて "9" がalertされた時と同様、
すべて10番目のspanが出力されるのを期待しました。

window.onload = function(){
var tags = [];
for(var i=0; i<SIZE; i++){
var el = document.createElement("span");
el.appendChild(document.createTextNode(i))
el.onclick = function(index){
return function(){alert(index);}
}(i);
tags[tags.length] = el;
}

for(var i=0; i<tags.length; i++){
document.body.appendChild(tags[i]);
document.body.appendChild(document.createElement("br"));
}
}

結果は、、、0, 1, 2, … と、また裏切られた…。
var index = i って書くとloopの間に上書きされるから、
その理屈だと、配列にセットされたそれぞれのelも
上書きされるはずなのに…?

もしかして、var 宣言に対する知識が足りてないのかな。。。
ちょっと混乱中ですw
そっか、そっか、そうでしたね!
手元にないけど、確かにオラ本で見ました。
読んだときは「ふ〜ん」って程度にしか思ってなかったですけど、
実際に痛感しないと身につかないものですね(笑)

for文の中は、別のスコープだろ?って思ってたのですが、
独立してなかったんですね。。。
なんだか嫌な仕様だけど(笑)

となると、3で書いたことも、
「値の参照かポインタの参照か?」って問題になりそうですね。
だいぶすっきりしてきました。
人形師さん、ありがとうございます!
ん、そら、
tags[tags.length] = el;
は普通に el = document.createElement("span"); が入りますよー。(笑)
クロージャとか関係ないし。

tags[tags.length] = i;
ってやっても普通に 1 2 3 4 5 6 〜 ってとれると思いますし。
これができなきゃさすがに困ってしまいます。

値を onclick とかで "その関数以外で" 参照するときにどうするか、ってことですよね。
人形師さんがおっしゃってたように、変数はその関数内ではグローバルなので、その関数以外である変数を参照するときにはその変数の最終的な値がとられちゃう、ってイメージですかね。
あんまりうまく言えませんが。
>>1 の下のほうで言ったことは忘れてくださいw
あ〜、すみません。ごめんなさい。紛らわしかったですね。
3の後半は、「スコープが妙だ…」って話だったので、
クロージャは直接関係なくて、onclickの記述も不要でしたorz

連想配列(配列も)のプロパティ値は、
おおよそJavaでいうpremitive型の場合は値が格納されるけれども
基本的にはオブジェクトのポインタが格納される、ってことで
試しに一旦配列に放り込んだのですが、
見直してみたら、var el = document.createElement("span");
の時点で別ポインタに移ってるから、意味なかったですね(笑)
挙動不審でスミマセンでした;

> 「値の参照かポインタの参照か?」って問題になりそうですね。

いや、そ〜いう問題じゃない。 と自己レス。

なかなかググりづらい話なので、「変な動きだな…」と
漠然と解釈してました。
みなさまに相談にのっていただけて良かったです。
ありがとうございました(^−^)

ログインすると、みんなのコメントがもっと見れるよ

mixiユーザー
ログインしてコメントしよう!

JavaScript 更新情報

JavaScriptのメンバーはこんなコミュニティにも参加しています

星印の数は、共通して参加しているメンバーが多いほど増えます。