JavaScript var/let/constによる変数宣言

プログラム未経験の知人にJavaScriptを教えていた所、「varとletとconst、何が違くて、どう使い分ければいいのかわからない」と言われ、私なりにもう一度深く調べたので、上記についてまとめます。

#var(変数)
再宣言・再代入が可能で、古いブラウザでも動く(ES5以下でも動く)。
スコープは関数スコープで、以下のようなコードが書ける。

```javascript
function (){
    var x = 0;
    console.log(x); //0が出力
    var x = 1;
    console.log(x); //1
    x = 2;
    console.log(x); //2
    if (true){
        console.log(x); //2
        x = 3;
        var y = "a";
    }
    console.log(x); //3
    console.log(y); //a
}

function (){
    console.log(x); //error x is not defined
}
```
関数スコープなので、varで定義した変数はその関数内でなら呼び出し可能で、再定義`var x = 1;`や、再代入`x = 2;`が可能。
if文の中で値を変更して、そのブロックを抜けても値は保持されたまま。
もちろん、2つ目の無名関数は変数xのスコープにないのでエラーが発生します。

#let(変数)
再宣言は不可、再代入は可能。大体のブラウザで動くが、IE9とかだと動かない。(ES6以上でないと動かない)。
[Babel](https://babeljs.io/)とか使うと古いブラウザにも対応するコードに変換してくれるが、今回の趣旨とは違うため説明はしない。
スコープはブロックスコープ。以下のコードを参照。

```javascript
function(){
    let x = 0;
    console.log(x); //0
    let x = 1; //error  x has already been declared
    console.log(x); //0
    x = 2;
    console.log(x) //2
    if (true){
        console.log(x); //2
        x = 3;
        let y = "a";
    }
    console.log(x); //3
    console.log(y); //error y is not defined
}

function (){
    console.log(x); //x is not defined
}
```
１つ目の無名関数内で定義した変数xは、そのブロック内にスコープがあるので、if文の中もスコープ範囲であること、再宣言が不可であることがわかります。
そしてif文の中で定義した変数yはif文のブロック内にしかスコープを持たないので、その外の関数では使えないことがわかります。
もちろんこちらも、2つ目の無名関数内ではエラーが起こります。

#const(定数)
再宣言、再代入が不可で、ブロックスコープ。
letが動くならconstも動く(ES6以上)。以下のコードを参照。

```javascript
function (){
    const x = 0;
    console.log(x); //0
    const x = 1; //error x has already been declared
    console.log(x); //0
    x = 2; //error Assignment to constant variable.
    console.log(x); //0
    if (true){
        console.log(x); //0
        const y = 3;
    }
    console.log(y); //error y is not defined
}

function (){
    console.log(x); //error x is not defined
}
```
このように、再宣言、再代入をしようとするとエラーが起こってしまいます。
関数型言語などでは、そもそも再代入不可がデフォルトのものもあったり、再代入するとバグを生みやすくなるよと警告を出してくれるものもあります。ただし、JavaScriptで重要な概念の一つにオブジェクトというものが存在します。
constを使うと、再宣言再代入が不可という特徴から、**constで定義されたものは絶対に変化しない**と思いがちになりますが、オブジェクトを操作することは可能です。具体的には以下のコードが書けるということです。

```javascript
function (){
    const like = {fruit: "apple", movie:"GODZILLA", food:"soba"};
    console.log(like); //{fruit: "apple", movie: "GODZILLA", food: "soba"}
    like.food = "rice";
    console.log(like); //{fruit: "apple", movie: "GODZILLA", food: "rice"}
    like = {fruit: "grape", movie: "GODZILLA", food: "rice"}; //これはNG
}
```
言語によって様々な機能や、ちょっとした特性など、知らないとバグの要因になってしまうことは沢山あります。
自身がよく使う言語の特徴はしっかり把握しておきましょう。

#変数の巻き上げ
恐らく、JavaScript特有の概念だと思いますが、変数宣言に関連した挙動で、変数の巻き上げ(hosting)というものが存在します。
例えば、このコードを見てください。(@Kobecow さんのコメント通りに修正しました)

```javascript
var x = 0;
function (){
    console.log(x);
    var x = 1;
    console.log(x);
}
```
一つめの`console.log(x);`では、何が出力されると思いますか？
普通に考えていくと、0が出力されるように見えますが、実際はundefinedが表示されます。
0でもなく、error x is not definedでもなく、undefinedです。
これがJavaScriptの巻き上げという挙動なのですが、簡単に説明すると、**JavaScriptは関数が宣言された時点で、その中でvarで宣言されてる変数をすべてundefinedで初期化する**というものです。
その結果、上記コードだと、思いもしない結果が出力されるわけです。
また、varが再宣言再代入可能で、letは再代入のみ可能だという挙動も、この変数の巻き上げによるものです。

```javascript
function (){ //ここでxとyがundefinedで初期化される。varで変数宣言されているから。
    var x = 0;
    var y = 1;
    let z = 2; //zはここで初期化される。
}
```
実際、大体の言語は同名の変数を、その名前空間の中で新しく宣言するのは原則として禁止されています。
JavaScriptはvarのみ、関数の頭で初期化されているので、`var x = 0;`は再宣言というより、コード内部では再代入として扱われているわけです。

#使い分け
基本的にはvarは使わないで、let/constを使いましょう。
constは確実に変わらない事が分かっている値や、プリミティブな値に意味を持たせたりする時に用いることが多いです。
例えば`getActor(5);`と書かれていた時、「この`5`は何を意味するの?」という疑問を無くすため`const LEADER_ACTOR_ID = 5`などと書く事で、`5`というプリミティブな値に意味をもたせる事が出来ます。

そして命名規則ですが、基本的にプロジェクトや自身の好みによって変わる事が多いです。
目安としてですが、constはアッパーケース、letはキャメルもしくはスネークケースを使う事が多いです。
また、モダンなJavaScriptで使われるクラスなどはパスカルケース、HTMLなどの要素に与えるクラス名はケバブケースと呼ばれる規則を使う事もあります。

```javascript:example
const HOGE_FUGA = 1; //アッパーケース
let hogeFuga = 1; //キャメルケース
let hoge_fuga = 1; //スネークケース

class HogeFuga {
} //パスカルケース

document.getElementyByClassName("hoge-fuga"); //ケバブケース
```