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

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

ただいまの
回答率

87.59%

constがインクリメントできない理由

解決済

回答 5

投稿

  • 評価
  • クリップ 5
  • VIEW 4,280

score 19

for文でのconstとletについていくつか質問があります。

const characters = [
       {name: "田中", age: 14},
       {name: "佐藤", age: 100},
       {name: "久保", age: 5},
       {name: "飯田", age: 19}
    ];

for (let i = 0; i < characters.length; i++) {

     const character = characters[i];
     console.log(`${character.name}`);

}

上記のコードのループを疑似的に書くと以下のようになります。

          //1回目のループ
       { 
           const character = characters[i];
           console.log(`${character.name}`);
       }

       //2回目のループ
       { 
           const character = characters[i];
           console.log(`${character.name}`);
       }

         //3回目のループ
       { 
           const character = characters[i];
           console.log(`${character.name}`);
       }


ここで質問があります。
インクリメントされる変数iはどこでインクリメントされるのでしょうか。
ループのたびに生成する別々のブロックの内部でインクリメントされるのでしょうか。
constがインクリメントできない理由は、ループのたびに生成する別々のブロックの外部でインクリメントするための変数iを定義するため,
ブロック内部で定義できるconst character = characters[i]; のように動かないのでしょうか。

よろしくお願いします。

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 5

checkベストアンサー

+6

let i = 0;
if(i>=character.length) break;
          //1回目のループ
       { 
           const character = characters[i];
           console.log(`${character.name}`);
       }
i++;
if(i>=character.length) break;
       //2回目のループ
       { 
           const character = characters[i];
           console.log(`${character.name}`);
       }
i++;
if(i>=character.length) break;
         //3回目のループ
       { 
           const character = characters[i];
           console.log(`${character.name}`);
       }


こうじゃないですかね

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2019/06/21 10:21

    if文で表現されると、すっごい動きわかりやすいですね・・・。
    そして、構文の最後の"ド"を消し忘れてらっしゃいますよ 笑

    キャンセル

  • 2019/06/21 13:35

    ド…
    ありがとうございます

    キャンセル

  • 2019/06/21 16:45

    キャンセル

  • 2019/06/21 19:05

    この擬似コードだと、それぞれのループでは変数iがブロックスコープを持たないことになるので、誤解が起きそうですね。

    キャンセル

+6

ECMAScriptの仕様を見る限り、for文の実行のされ方はletの場合とconstの場合で明確に場合分けされているので、そこを見ない限り理由はわからないと思われます。

Runtime Semantics - The for Statement - ECMAScript 2018 Specification
(letconstを用いたforに該当するのは "for(LexicalDeclaration Expression; Expression) Statement" の部分で、isConstを用いて場合分けされている)

Note: 以下は私なりの仕様の解釈ですが、間違っているかもしれません。

for (let ...)は、イテレーションごとに別々のブロック(正確にはEnvironment)が生成され、各ブロックでループ変数が新しく作られ、一つ前のブロックでの最終的なループ変数の値がコピーされます。インクリメントは最初以外のイテレーションで、ループ変数が作られた直後に実行されます。

for (let i = 0; i < characters.length; i++) {
  const character = characters[i];
}


は以下のように実行されます。

{
  let i = 0;
}

{
  let i = 一つ前のブロックの最終的なiの値;
  // test
  if (i < characters.length) ループから抜ける;
  // body
  {
    const character = characters[i];
  }
}

{
  let i = 一つ前のブロックの最終的なiの値;
  // increment
  i++;
  // test
  if (i < characters.length) ループから抜ける;
  // body
  {
    const character = characters[i];
  }
}

{
  let i = 一つ前のブロックの最終的なiの値;
  // increment
  i++;
  // test
  if (i < characters.length) ループから抜ける;
  // body
  {
    const character = characters[i];
  }
}
// ...(以下繰り返し)...

一方for (const...)の場合はループごとに新しいブロックは作られず、すべて同じブロックで実行されます。上の例だと以下のように実行されます。iconstで宣言されているので当然最初のi++で実行時エラーとなります。

{
  const i = 0;

  /* ---------- */

  // test
  if (i < characters.length) ループから抜ける;

  // body
  {
    const character = characters[i];
  }
  // increment
  i++;

  /* ---------- */

  // test
  if (i < characters.length) ループから抜ける;
  // body
  {
    const character = characters[i];
  }
  // increment
  i++;

  /* ---------- */

  // ...(以下繰り返し)...
}

参考リソース

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

+4

質問終了済みに回答してすみません。
papinianusさんのご回答はわかりやすいのですが、実際のforループとスコープ周りの動作が異なりますのでご注意ください。karamarimoさんのご回答が参考になると思います。

const characters = [
       {name: "田中", age: 14},
       {name: "佐藤", age: 100},
       {name: "久保", age: 5},
       {name: "飯田", age: 19}
    ];

//質問のコード(一部追加)
for (let i = 0; i < characters.length; i++) {

     const character = characters[i];
     console.log(`${character.name}`);
     setTimeout(function(){console.log(i)}, 1000);

}
/*
田中
佐藤
久保
飯田
0
1
2
3
*/


//papinianusさんのコード(一部追加)
loop:{
let i = 0;
if(i>=characters.length) break loop;
          //1回目のループ
       { 
           const character = characters[i];
           console.log(`${character.name}`);
           setTimeout(function(){console.log(i)}, 1000);
       }
i++;
if(i>=characters.length) break loop;
       //2回目のループ
       { 
           const character = characters[i];
           console.log(`${character.name}`);
           setTimeout(function(){console.log(i)}, 1000);
       }
i++;
if(i>=characters.length) break loop;
         //3回目のループ
       { 
           const character = characters[i];
           console.log(`${character.name}`);
           setTimeout(function(){console.log(i)}, 1000);
       }    
}
/*
田中
佐藤
久保
2
2
2
*/

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2019/06/24 14:13

    正確を期せばループから抜けるのは break のときのみなので、最後にもこれが必要な気がします。
    i++;
    if(i>=characters.length) break loop;

    キャンセル

  • 2019/06/24 14:30

    おっしゃるとおりです。

    break以外にループを抜けない、という意味でもっと正確に書くなら、

    i++;
    if(i>=characters.length) break loop;
    continue loop;

    かもしれません。
    そして、そこまでやるなら解説用としてもwhile文で書いた方がわかりやすいので、think49さんのご回答に行き着くかもしれません。

    解説用コードですから、誤解さえなければいいかな、ぐらいの感じで。

    キャンセル

+3

while 文

for-of -> for -> while の順に原始的でシンプルな繰り返し構文になっていくので、それらを上手く使って疑似コードを書いてみる事は有用だと思います。
原始的なコードは応用が利く分、発想の幅が広がります。

  • for 文よりも原始的な while 文で再現
  • for 文のブロックスコープは関数スコープで再現。関数 forLoop* に引数 i (= jk) が渡されると、複製された変数 i は関数スコープになるので、関数外からの干渉を受けません。 (Lhankor_Mhy さんが指摘されている性質)
'use strict';
function handleTimeout (characters, k) {
  const character = characters[k];
  console.log(`${character.name}`);
}

function forLoop1 (i) {
  const character = characters[i];
  console.log(`${character.name}`);
}

function forLoop2 (j) {
  setTimeout(function handleTimeout () {  // 分かりやすさ重視で関数をネストさせていますが、毎回、関数を生成するのは無駄なので、基本的には関数を外に追い出す事を推奨します
    const character = characters[j];
    console.log(`${character.name}`);
  }, 100);
}

function forLoop3 (k) {  // 関数 handleTimeout を外に追い出した場合
  setTimeout(handleTimeout, 100, characters, k);
}


const characters = [
  {name: "田中", age: 14},
  {name: "佐藤", age: 100},
  {name: "久保", age: 5},
  {name: "飯田", age: 19}
];

let i = 0;
while (i < characters.length) {
  forLoop1(i); // "田中" -> "佐藤" -> "久保" -> "飯田"
  i++;
}

let j = 0;
while (j < characters.length) {
  forLoop2(j); // "田中" -> "佐藤" -> "久保" -> "飯田"
  j++;
}

let k = 0;
while (k < characters.length) {
  forLoop3(k); // "田中" -> "佐藤" -> "久保" -> "飯田"
  k++;
}

※BabelのES5コンパイルも似たようなコードを生成していた記憶があります。

更新履歴

  • 2019-06-22 11:54:16 setTimeout のコールバック関数をネストしない forLoop3 を追記。ブロックスコープの説明を一文追記。

Re: Sano さん

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2019/06/22 11:35

    初見で「これも変数 i のスコープが違う」と思ったのですが、ブロックスコープをクロージャで再現している、と理解しました。よろしいですよね?

    キャンセル

  • 2019/06/22 11:59

    回答に追記しました。

    To: Lhankor_Mhy さん
    関数 forLoop2 に引数 j が渡された場合、変数 j は「関数 forLoop2 内にあるローカル変数 j」として再定義(複製)されます。
    関数 forLoop2 が呼び出された時点で変数 j は外部からの干渉を受け付けなくなっているので、そこから先のクロージャは無関係だと思います。
    forLoop3 のパターンでも変数 k は外部からの影響を受けません。

    キャンセル

  • 2019/06/22 12:02

    変数 characters に関してはスコープチェーン(回答のコードを「関数コード」と見るなら、クロージャ)で再現しています。

    キャンセル

  • 2019/06/22 12:03

    ああ、失礼。
    おっしゃるとおり、クロージャ、は違いましたね。

    キャンセル

+1

エムディエヌは加算のタイミングが文が実行された後といっていますょ

https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Loops_and_iteration#for_statement

for ループが実行されるとき、次の処理が行われます :

  1. もしあれば、初期化式が実行されます。この式は通常、1 個またはそれ以上のループカウンタを初期化しますが、この構文ではいかなるレベルの複雑な式を入れることが可能です。初期化式で変数を宣言することもできます。
  2. 条件式 条件式が評価されます。条件式の値が true の場合、ループ文が実行されます。条件式の値が false の場合、for ループは終了します。条件式がすべて省略されている場合、条件式は真であると仮定されます。
  3. 文が実行されます。複数の文を実行するには、それらの文をグループ化するためにブロック文 ({ ... }) を使用します。
  4. もしあれば、更新式 加算式が実行されます。
  5. ステップ 2 に制御が戻ります。

i++がi=i+1と同じだからiがconstになってぃると再代入エラァーになるんでゎないでしょぅか?
https://teratail.com/questions/96687

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

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

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

関連した質問

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