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

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

ただいまの
回答率

89.70%

JavaScriptの変数宣言について var と let の使い分け

解決済

回答 2

投稿

  • 評価
  • クリップ 5
  • VIEW 3,565

margutta

score 32

前提・実現したいこと

JavaScriptの変数宣言について var と let の使い分けについて

質問 51551:JavaScriptでradioボタンで選択して背景色を変える(for loop を使って)
https://teratail.com/questions/51551
に関連して、変数の宣言の仕方 let について調べました。

参考資料:
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/let#内部関数でのクリーンなコード

var や let の他にも、const という宣言の方法もあることがわかりました。

今のところ、let はすごく便利だった!という印象と、↑のような説明が、なんとなくわかるような気がするといったところなのですが、以前にvar で書いたものの中には、let や const では動かないものもあります。便利なことを知った分怖くなったところもあり、どのように使い分けたらよいのかを理解することができたらよいなと思っています。

発生している問題・エラーメッセージ

エラーメッセージ

該当のソースコード

1.let でしか動かない

<form id="myForm">
  <input type="radio" name ="color" value="green">green
  <input type="radio" name ="color" value="red">red
</form> 

<script>
var c = document.getElementsByName('color');  
for(let i=0; i<c.length; i++){
    c[i].onclick= function(){document.body.style.background = c[i].value;
                 }
    }
  </script>

2.var でしか動かない

<p id="demo">default</p>

<script>
  var flws = ["Rose", "Tulip", "Pansy"];
  for (var i = 0, text = ""; i < flws.length; i++) {
      text += flws[i] + "<br>";
  }
  document.getElementById("demo").innerHTML = text;
</script>

試したこと

課題に対してアプローチしたことを記載してください

補足情報(言語/FW/ツール等のバージョンなど)

関連質問:質問: JavaScriptでradioボタンで選択して背景色を変える(for loop を使って) 
https://teratail.com/questions/51551

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 2

checkベストアンサー

+7

varでしか動かないという2.のほうはtextをfor文内部のletで宣言しています。ですので、for文のあとにtextを使おうとする処理があるため、動かないのです。

varとletの使い分けはスコープだけ意識すればできると思います。まず、var宣言はすべて関数の最初に持ってきてください。varは関数の途中であっても関数の最初にあるものと見なされるため、動作は変わりません。関数の途中でvarを書くと言う習慣をなくせば、varのスコープに悩むことは無くなります。次に、変数宣言は一つだけにしてください。varやletは複数の変数宣言をまとめてできますが、分離して考えられないため、混乱の元に過ぎません。つまり、2.のコードは下記のようにします。

var flws = ["Rose", "Tulip", "Pansy"];
var i = 0;
var text = "";
for (; i < flws.length; i++) {
    text += flws[i] + "<br>";
}
document.getElementById("demo").innerHTML = text;

という形であれば、iはfor文の中だけで使っているので、letにしてfor文の中に押し込めますが、textはできないと言うことがわかると思います。

var flws = ["Rose", "Tulip", "Pansy"];
var text = "";
for (let i = 0; i < flws.length; i++) {
    text += flws[i] + "<br>";
}
document.getElementById("demo").innerHTML = text;

【追記】

1.の方はあまり言及してませんでした。letはブロックスコープです。ですので、ブロック内部に押し留まります。ちょっと例を書いてみます。

var list = [];
for (let i = 0; i < 10; i++) {
  let x = i * 2;
  list.push(function() {
    console.log(i, x); // それぞれのループで i と x は異なる。
  });
}
list[0](); // => 0 0
list[1](); // => 1 2
list[9](); // => 9 18

このコードのixをみてください。これらはlet宣言のため、for文のブロックの中でのみ有効です。for文のブロックは10回繰り返しますが、それぞれのブロックは独立しており、そのため、ixもそれぞれ10個作られます(ただしiは、次ループに行くときに以前のiの値を引き継ぎ、i++の処理をしてから別のiとして作られることになります)。listに入れられた無名関数の中のixもそれぞれが独立していることになります。

もし、これらがvar出会った場合は、関数(この場合は関数がありませんので、全体の先頭)にあるものとみなされます。

var list = [];
for (var i = 0; i < 10; i++) {
  var x = i * 2;
  list.push(function() {
    console.log(i, x); // 全体で一つの i と x しかないため、同じものになる。
  });
}
list[0](); // => 10 18
list[1](); // => 10 18
list[9](); // => 10 18

そのため、ブロック内部のixはそれぞれたった1個を十回のループで共通して使うようになります。こうなるとlistに入れていく無名関数の中のixは全て同じものを指すことになってしまいます。これでは同じ事になりません。

では、どうやって考えれば良いのか。それは、変数をどのスコープ(範囲)で有効にしたいのかです。varを使った場合、関数全体がスコープになり、関数全体でたった一つしかありません。新たな関数の中でさらにvarで宣言しない限り、常に同じものを指します。対して、letであればブロックの内側だけです。もし、そのブロックがfor文やwhile文などのループであれば、それぞれのループの中で独立して存在することになります。そのようなループで独立して使うには、別途何らかの関数を用意しない限り、varでは実現できません。他にも、各ブロックで同じ変数を使ってしまった場合の影響を避けるために、letを使うという手法があります。

最後に、もし、letの場合をletを使わずに等価な形にした場合どうなるか、ですが、下記のようになります(変換にはBabelを使用)。

"use strict";
var list = [];
var _loop = function _loop(i) {
  var x = i * 2;
  list.push(function () {
    console.log(i, x); // それぞれのループで i と x は異なる。
  });
};
for (var i = 0; i < 10; i++) {
  _loop(i);
}
list[0](); // => 0 0
list[1](); // => 1 2
list[9](); // => 9 18

_loop関数のiとfor文のiは異なるiです。_loop関数は10回呼び出されるため、ixも10個作られると言うことがわかるかと思います。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2016/10/16 18:17

    解答を書いてくださりありがとうございます。

    1. i (counta?) 以外の変数は、ループが始まる前に、とりあえず var で定義しておく.
    2. for()の()内で、i の初期値を定義するときは、let で定義しておけば、再代入が必要になったときでも対応できる.

    まだ、どういう場合が、再代入なのかはわかっていないのですが、とりあえず、↑のように理解し、↓のように書いておけば、自分なりに納得できるのですが、問題ないでしょうか?

    ○ https://teratail.com/questions/51569 の場合
    <script>
    var flws = ["Rose", "Tulip", "Pansy"];
    var text="";
    var len=flws.length;
    for (let i = 0 ; i < len; i++) {
    text += flws[i] + "<br>";
    }
    document.getElementById("demo").innerHTML = text;
    </script>


    ○ https://teratail.com/questions/51551 の場合

    <script>
    var c = document.getElementsByName('color');
    len=c.length;

    for(let i=0; i<len; i++){
    c[i].onclick= function(){document.body.style.background = c[i].value;
    }
    }

    </script>

    お忙しい中、回答を書いてくださり、ありがとうございました。自分一人では気付けないことを教えていただき感謝しています。

    キャンセル

  • 2016/10/16 19:30

    1.について余り書いてませんでしたね。追記しました。

    iだから…ではなく、iをどこで使いたいかです。関数全体で一つなのか、ブロックの中だけで各ループではそれぞれ別のもとして扱いたいのか。見える範囲を意識するとletはどう使うのかが意識できると思います。

    キャンセル

  • 2016/10/16 22:47

    この説明を胸に自分の成長を待ちたいと思います。ありがとうございました!

    キャンセル

+1

2.var でしか動かない

書くならこうとか。

const flws = ["Rose", "Tulip", "Pansy"];
let text = "";
for (let i = 0; i < flws.length; i++) {
    text += flws[i] + "<br>";
}
document.getElementById("demo").innerHTML = text;

動くサンプル:https://jsfiddle.net/90fgtrgq/


【JavaScriptにおけるvar/let/constの使い分け】
https://sbfl.net/blog/2016/07/14/javascript-var-let-const/

【var, let, constの使い分けについて|もっこりJavaScript|ANALOGIC(アナロジック)】
http://analogic.jp/var-let-const/

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2016/10/16 18:19

    let を 教えていただき、大変ありがたかったです。教えていただいた資料もじっくり拝見したいと思います。ありがとうございました。

    キャンセル

  • 2016/10/16 18:27

    「再代入」については、下記のようなことを指します。
    var a = 0;
    a = 1; // 再代入

    「const 」は再代入禁止なので出来ません。コードの中で再代入する必要が無い変数というものは結構多いので、それに使います。
    const b = 0;
    b = 1; // エラー「TypeError: invalid assignment to const `b'」

    キャンセル

  • 2016/10/16 19:19

    納得!! できた!! と思います。
    あまたの本で、var が、幅を利かせている、ということのほかに、再代入をすごく難しく考えていてしまっていたので、
    わかりませんでした。スッキリした感じがします。

    1つだけ、なんとなく、もやっとしているのは、51551(https://teratail.com/questions/51551)で、
    「onclick を実行する時点で変数 i が c.length(2?or c.length)になっている」とおっしゃっていらしたところ。
    それが、var を let にかえると直ってしまうのはどうしてなんだろう....と、その辺のわけは、まだちょっとわからないままでいます。

    ありがとうございました。

    キャンセル

  • 2016/10/16 20:09

    raccyさんの追記に回答が追記されていますので、そちらを参考にしてみてください。「スコープ」が重要です。

    キャンセル

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

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