人力検索はてな
モバイル版を表示しています。PC版はこちら
i-mobile

以下のJavascriptのプログラムを実行すると2回とも10のメッセージが表示されます。なぜ、0と9にならないのかどなたかご説明いただけないでしょうか。

var a = new Array();
function f(){
for(var i = 0;i < 10;i++){
a[i] = function(){
window.alert(i);
};
}
}

f();
a[0]();
a[9]();

●質問者: dambiyori
●カテゴリ:ウェブ制作
✍キーワード:JavaScript プログラム メッセージ
○ 状態 :終了
└ 回答数 : 3/3件

▽最新の回答へ

1 ● しいたけ
●80ポイント

window.alert(i);

が実行されるのはf()が終了(forループが終了、i=10。)したあとだからだと思います。

f(); ※forを10までまわしてf()が終わり。 ※このときi=10

a[0]();でwindow.alert(i); ※このときi=10

a[9]();でwindow.alert(i); ※このときi=10

◎質問者からの返答

そういうことですよね。

無名関数の扱いとかの辺りのもうすこしつっこんだ解説やURLを教えていただけるとありがたいです。

あと、逆に「このプログラムを0と9が表示されるように作り変えるとしたらどうしたらよいか」も募集してみますのでよろしくお願いします。


2 ● じゃっくそにっく
●80ポイント

このソースにおいて

<script language="javascript">
var a = new Array();

function f(){
 //? i が10になったら終了
 for(var i = 0;i < 10;i++){
 a[i] = function(){
 window.alert(i);
 };
 }
 // 地点B ( 変数i の値は 10 )
}

f();
// f()が実行されたため、地点Bを通る

// 「i」という名前の変数が他に無いので、この地点で
// 「i」という名前で参照される値は「10」になる
a[0]();
a[9]();
</script>

■解説

a[0]?a[9]にalertを使った関数を定義するループ?において、

このやり方ですと、埋め込まれているのは、

その時の「i」の値(固定値)ではな「「i」という変数をリアルタイムに参照せよ」

というコードであるため、

処理の内容は、呼び出されたそのときリアルタイムに

「i」という名前の変数を探し出し、

値をとってきてalertで表示する」

という処理を定義することになります。

そのため、「定義をしているときに、何回目のループで、

「i」の値はいくつだったか」

という情報は反映されません。

あくまで、

「リアルタイムに「i」を探し出せ」という

命令を組み込んだ関数ということになっています。


そのため、ループ後に

a[0]を呼んでもa[9]を呼んでも、

「リアルタイムに「i」を探し出す」

という全く同じ処理をしているだけなので、

for文の条件(i <10)から、

ループが終わったとき、「i」の値は「10」になっており、

2つとも「10」が表示されます。

■異なるものを表示させるには?

ループ回転中の異なるループ変数「i」の値を反映した処理をさせるには

次の方法を使います。

<script language="javascript">
var a = new Array();

function f(){
 // i が10になったら終了
 for(var i = 0;i < 10;i++){
 a[i] =function()
 {
// 式を文字列化すれば、iの値を反映できる。
eval("window.alert(" + i + ");")
 }
 }
}

f();
a[0]();
a[9]();
</script>

このように、ループ変数「i」ごとに処理を変化させる部分は、

文字列式で処理を定義し、

eval()で呼び出すようにすることで、

異なる処理をさせることができます。

文字列は、命令コードのようにリアルタイムに探し出すのではなく、

定義された時点で固定されるからです。

こちらのWikiにも書きます。

JavaScript/ループを使った関数定義のテクニック - ジャックズラボ jack's Lab

◎質問者からの返答

やっぱりiが評価されるタイミングというのが肝になるんですかね。

eval()を使った解決法は、デモンストレーション的ですね。


3 ● susie-t
●80ポイント

提示されたコードでは、配列aのメンバはすべて同じ関数への参照です。なので、同じ挙動しかしません。

以下のようにすればできます。

var a = new Array();
function f(){
 function make(j){
 return function(){
 window.alert(j);
 };
 }
 for(var i = 0; i < 10; i++){
 a[i] = make(i);
 }
}
f();
a[0]();
a[9]();

各関数を別に生成するため、関数makeを作成しています。

親関数内の子関数(クロージャ)は、親関数の実行ごとに生成されるという性質があります。

参考:http://d.hatena.ne.jp/keyword/%A5%AF%A5%ED%A1%BC%A5%B8%A5%E3

参考になりますでしょうか。

◎質問者からの返答

なるほど、こんな風に書けるんですね。「クロージャ」がキーワードなのかな。

関連質問


●質問をもっと探す●



0.人力検索はてなトップ
8.このページを友達に紹介
9.このページの先頭へ
対応機種一覧
お問い合わせ
ヘルプ/お知らせ
ログイン
無料ユーザー登録
はてなトップ