以下の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]();

回答の条件
  • 1人5回まで
  • 登録:
  • 終了:2007/05/26 00:09:24
※ 有料アンケート・ポイント付き質問機能は2023年2月28日に終了しました。

回答3件)

id:tiga No.1

回答回数107ベストアンサー獲得回数0

ポイント80pt

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

id:dambiyori

そういうことですよね。

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

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

2007/05/25 22:47:10
id:jack_sonic No.2

回答回数124ベストアンサー獲得回数25

ポイント80pt

このソースにおいて

<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」の値を反映した処理をさせるには

次の方法を使います。

  • ループ中に関数を定義するとき、文字列式とevalを使います。
<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

id:dambiyori

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

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

2007/05/25 23:49:29
id:susie-t No.3

回答回数99ベストアンサー獲得回数18

ポイント80pt

提示されたコードでは、配列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

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

id:dambiyori

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

2007/05/26 00:04:01
  • id:tiga
    >「このプログラムを0と9が表示されるように作り変えるとしたらどうしたらよいか」

    不親切すぎました、すみません。
    http://iandeth.dyndns.org/mt/ian/archives/000627.html
    クロージャー(?)でいけそう・・・って思ったらすでにsusie-tさんが書かれていますね。
    ※make(j)はf()の外でもOKかも
  • id:dambiyori
    >不親切すぎました、すみません。
    いえいえ、私も論点がよく分からないまま投げてみたので、むしろ答えていただいてありがとうございます。

    >make(j)はf()の外でもOKかも
    今まで、そういうやり方してたんですよ。なんだかちょっと頭の中が整理できました。あと、勉強する取っ掛かりも。
  • id:jack_sonic
    >やっぱりiが評価されるタイミングというのが肝になるんですかね。
    >eval()を使った解決法は、デモンストレーション的ですね。
    そうですね。
    文字列化+evalを使ったものは、今回のケースにおける
    割り当てと評価タイミングを理解する用的な色が強いです。
    特に必要がなければ
    クロージャの概念を新たに理解して使えればといいかと思います。
  • id:susie-t
    すみません、私の回答で

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

    というのは間違いです。。。 関数は別でした。ただし、iは同じものを参照するので、動作が一緒になるということです。

この質問への反応(ブックマークコメント)

トラックバック

「あの人に答えてほしい」「この質問はあの人が答えられそう」というときに、回答リクエストを送ってみてましょう。

これ以上回答リクエストを送信することはできません。制限について

回答リクエストを送信したユーザーはいません