質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.52%

JavaScriptのfor文の中での関数実行

受付中

回答 7

投稿

  • 評価
  • クリップ 3
  • VIEW 570

3905

score 0

JavaScriptのfor文の中での関数実行に関する質問です
質問は、なぜ関数がfor文の回数分実行されるのかについてです。

参考サイトhttps://qiita.com/ukiuni@github/items/463493a690265cec8bb7

for (var i = 0; i < 3; i++) {
    setTimeout(function() {
        console.log(i);
    }, 0);
}

様々なサイトで、for文実行中は関数が実行されずに、終了後にiの最後の値を参照して実行されるという記述を見ました。しかし、for文が終了した後に、関数の実行回数がfor文の回る回数と同じになっていることが理解できません。for文実行中に、○○回回ったという情報がどこかに記憶されて、for文終了後に関数を実行する際、iは最後の値だけを見て、回数はその記憶から引っ張り出してくるから、iは最後の値のまま5回回るという結果になるのでしょうか?
クロージャなどがまだ理解できず、すごく単純に以下のように考えています。
・for文が終わったのであれば、関数の実行は1回だけのはず。なぜならforという繰り返し処理が終わっているのだから
・しかし実際はfor文のブロックの中に関数がいるので、関数は繰り返し処理に含まれている
・繰り返し処理に含まれていて、関数の外にある変数も参照できるのあれば、iも参照できるはずで、iを参照できるなら、iの数が変わったことも関数内でわかるはず
なぜ関数はfor文の回数分実行されるのでしょうか?

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • m.ts10806

    2019/12/18 12:07

    単にforの回数だけsetTimeoutが実行されている処理にしか見えませんけど…

    キャンセル

  • Takumiboo

    2019/12/18 12:40

    for文の中では関数は実行されないなんてどこに書いてあるんですか…。

    キャンセル

回答 7

+3

【JavaScript入門】setTimeoutの使い方とサンプル事例まとめ!

0ミリ秒と指定されたsetTimeoutはすぐに実行されるように思いますが、先にメインの実行処理が行なわれるのです。

ただこういう理由なだけです。
その時の変数の動きについてはご提示のQiitaに書かれている通りですね。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

+3

setTimeoutは「何ミリ秒にこの関数を実行する」予定を積む関数です。そして、予定として積まれた各関数は、同じiを参照しますので、実行前にiがループを抜けきって5となっていれば、その5を表示します。

これに対して、ループ変数をletで宣言した場合、それはループ1回ごとに別個の変数として扱われますので、setTimeoutの中のiが紐づくのも、ループ1回ごとに確保された変数となりますので、登録時のiの値がそのまま残ることとなります。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/12/18 13:11 編集

    JavaScriptはシングルスレッドですので、同期的な処理が続いている間はsetTimeoutの中身の関数は実行されません。forループが長引いても、各関数の実行はループの終了より後です。

    キャンセル

  • 2019/12/18 13:16 編集

    addEventLitenerは同じ関数を複数回登録してもイベント1回につき同じ関数は1度しか実行されませんが、setTimeoutを使って同じ関数を複数回登録すると、登録した回数だけ実行されます。

    キャンセル

  • 2019/12/18 14:16

    > addEventLitenerは同じ関数を複数回登録しても
    質問文のようなコードの場合複数回登録したコードが、登録した回数だけ実行されませんか?「同じ関数」と書くなら例示しないとわかりにくいと思います。
    https://jsfiddle.net/wtezu6yh/1/

    キャンセル

+3

どんな処理が順番に実行されるか、流れを簡単な言葉で記します。

  1. イベントループ開始
  2. i = 0 (for文開始)
  3. setTimeout実行 0秒後のイベントループ(0なので今回は次)で実行されるようにタスクキューとして溜め込む
  4. i++
  5. setTimeout実行 0秒後のイベントループにタスクキューとして溜め込む
  6. i++
  7. setTimeout実行 0秒後のイベントループにタスクキューとして溜め込む
  8. i++ (for文終了)
  9. やることなくなったら、次のイベントループまで待機
  10. イベントループ開始
  11. タスクキューがあるのでコールスタックに追加
  12. 以下コールスタックを実行
  13. console.log(i) 1回目のsetTimeoutで登録されたタスクを実行
  14. console.log(i) 2回目のsetTimeoutで登録されたタスクを実行
  15. console.log(i) 3回目のsetTimeoutで登録されたタスクを実行

以上、こんな感じの動作になっています。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

+2

貼っていただきました参考サイトでは
for文の中でsetTimeoutを使った場合のケースを説明しており、
for文の実行後にsetTimeoutが3回実行されるため
すでに変数iには3が入っているので、3の出力が3回あると記載されています。

for文実行中は関数が実行されず」
という解釈は勘違いかと思います。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/12/18 13:20

    私自身混乱を招いてしまってますが
    関数という主語が大きいせいで、
    setTimeout関数 と表記をしていればもう少しわかりやすかったかもしれません。

    キャンセル

+1

○○回回ったという情報がどこかに記憶されて

for の初期化部分(initialization)で宣言されている i に記憶されています。

・for文が終わったのであれば、関数の実行は1回だけのはず。

forの繰り返しで、setTimeout() を 3回実行しており、時限実行する関数 は3つ設定されています
(3つとも、for が終わってから実行されるように予定されます)。

iを参照できるなら、iの数が変わったことも関数内でわかるはず

時限実行する関数は、外側のブロックにある変数 i を参照しますが、
for の初期化部分(initialization)で

  1. var 宣言されたものだと、実行時の値を参照します(3, 3, 3)。
  2. let 宣言されたものだと、for ループのブロック内の記述に応じた値を参照します(0, 1, 2)。
// let で宣言した場合
for (let i = 0; i < 3; i++) {
    setTimeout(function() {
        console.log(i);
    }, 0);
}


この違いは スコープ やブロック を理解すると疑問も解消できるのではないかと思います。


クロージャなどがまだ理解できず

クロージャは、変数を var 宣言しかできなかった時代に、外部から変数の値を書き換えられることがないようにする 特殊な関数生成の手法です。古いコードを読み解く時には必要ですが、letconst が使える現代は、慌てて覚える必要は無くなっていると思います。
局所変数宣言のletと 定数宣言のconst を先に覚えましょう。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

setTimeoutが外の変数を参照しているからですね
おそらく非同期プログラムの最適化の恩恵だと思います

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/12/18 12:47 編集

    ここ2-3日ちょっとながい回答が全然アップできない
    本文が更新できないのでレスでいくつかサンプル

    キャンセル

  • 2019/12/18 13:10

    (1)letで処理
    for(let i = 0; i < 3; i++) {
    setTimeout(()=>{
    console.log(i);
    },0);
    }

    キャンセル

  • 2019/12/18 13:10

    (2)引数で渡す
    for(var i = 0; i < 3; i++) {
    console.log(x);
    },0,i));
    }

    キャンセル

  • 2019/12/18 13:10

    (3)同期で処理する
    (async()=>{
    for(var i = 0; i < 3; i++) {
    await new Promise(resolve=>setTimeout(()=>{
    resolve(i);
    },0)).then(console.log);
    }
    })();

    キャンセル

0

for ループで、3回setTimeoutが呼び出されるので、「0秒後にfunction内の処理を実行する」というタイマーが3個セットされることになります。直感的には、0秒後なのでforループ中にすぐに実行されるように思えますが、JavaScriptの仕様上そうはなりません。forループを抜けた後に3個のタイマーが実行されます。これがforループを抜けた後に関数が3回実行される要因となります。
また、関数内で表示されるiの値は、forループを抜けた後なので3になっています。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.52%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る