【JavaScript】var / let / const を本気で使い分けてみた

## はじめに
なんとなーくで`let`/`const`を使い分けていたが
間違った理解をしていたので、本気で使い分ける方法をまとめてみました。

## ざっくりした結論
* ほぼ全部`const`で定義できる
* 数え上げのみ`let`
* varはもう使わない

> 参考：[JavaScriptの変数宣言『let, var, const』の使い分けについて【プログラミング小話】](https://youtu.be/yWDmFmCM7fw)

## 細かい仕様の説明
### `var`/`const`/`let`の違い

<table>
  <thead>
    <th></th>
    <th>const</th>
    <th>let</th>
    <th>var</th>
  </thead>
  <tbody>
    <tr>
      <th>再宣言</th>
      <td>×</td>
      <td>×</td>
      <td>○</td>
    </tr>
    <tr>
      <th>再代入</th>
      <td>×</td>
      <td>○</td>
      <td>○</td>
    </tr>
    <tr>
      <th>スコープ</th>
      <td>ブロック</td>
      <td>ブロック</td>
      <td>関数</td>
    </tr>
    <tr>
      <th>ホイスティング</th>
      <td>エラー</td>
      <td>エラー</td>
      <td>undefined</td>
    </tr>
  </tbody>
</table>

#### 再宣言
一度宣言した変数を、同じ変数名で宣言し直すことを`再宣言`といいます。
再宣言可能な`var`で再宣言した場合、後に宣言した変数が適用され、
再宣言不可能な`let/const`で再宣言した場合、エラーになります。
`var`では、予期しない再宣言が起こりうるため危険です。

```js
// var
var a = 0;
var a = 1; 
console.log(a) // `1`と出力される

// let
let b = 0;
let b = 1; // SyntaxError: Identifier 'b' has already been declared

// const
const c = 0;
const c = 1; // SyntaxError: Identifier 'c' has already been declared
```

#### 再代入
宣言した変数に値を設定し直すことを`再代入`といいます。
再代入可能な`var/let`で再代入した場合、値が再代入され、
再代入不可能な`const`で再代入した場合、エラーになります。


```js
// var
var a = 0;
a = 1; 
console.log(a) // `1`と出力される

// let
let b = 0; 
b = 1;
console.log(b) // `1`と出力される

// const
const c = 0;
c = 1; // Assignment to constant variable. 
```

#### スコープ
実行中のコードから参照できる範囲を`スコープ`といいます。
`const/let`はブロックスコープ(`{}`で囲われた部分 - if文やfor文など)が適用されますが、`var`はブロックスコープが適用されません。
※ 関数スコープ(関数宣言の`{}`)は、`var/const/let`すべてに適用されます。

```js
/*
 * var
 */
{
  var a = 0;
}
// ブロックスコープが適用されないため、ブロック外でも値の参照が可能
console.log(a); // 0


/*
 * let
 */
{
  // ブロックスコープにより、再宣言にならない。
  let a = 1;
  console.log(a); // 1
}
// letはブロックスコープであり参照できないため、varで宣言した値が参照される。
console.log(a); // 0

/*
 * const
 */
{
  const b = 2;
  console.log(b); //2
}
console.log(b) // b is not defined 
```

#### ホイスティング(変数の巻き上げ)
変数宣言が常に関数の先頭で行われたことにされる挙動を**ホイスティング(変数の巻き上げ)**といいいます。

```js
// var
{
  console.log(a); // undefined
  var a = 0; 
  console.log(a); // 0
}

// let / const
{
  console.log(b); // Cannot access 'b' before initialization 
  const b = 0; 
  console.log(b); // 0
}
```

### データ型の種類と参照方法
`var`/`const`/`let`の変数宣言を使い分けるには、データ型の種類と参照方法を知っておく必要があります。

#### データ型の種類
データ型には、大きく分けて**プリミティブ型**と**オブジェクト**があります。

* プリミティブ型（基本型）
  * Boolean - true または false
  * Number - 数値
  * String - 文字列
  * null - 空(参照を保持していない状態)
  * undefined - 未定義
  * BigInt (長整数) - 桁が多い数値(ES6)
  * Symbol (シンボル) - 一意の値(ES6)
* オブジェクト（複合型） - プリミティブ型以外全部
  * オブジェクトリテラル
  * 配列リテラル　　など

> 詳しい参考：[データ型とリテラル - JavaScript Primer](https://jsprimer.net/basic/data-type/)


#### データ型の参照
プリミティブ型とオブジェクトでは、参照方法が異なります。

##### プリミティブ型の場合
再代入の際に参照先が変更されます。

【図解 - 文字列の再代入の例】
![Web 1920 – 1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142322/a0ed34e6-76d9-b958-033a-dc7c238c3107.png)


##### オブジェクトの場合
変数に対して参照するオブジェクトが変更された際に再代入になります。
**オブジェクト内のキーや値が変更されても、変数に対する参照先が変わらないため再代入にはなりません。**

【図解 - オブジェクトの値変更・再代入の例】
![Web 1920 – 2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142322/492bd896-84ff-cd0e-2d3f-b7d3d04f2086.png)


## もう一度、結論
* ほぼ全部`const`で定義できる
    * オブジェクトや配列の値の変更は再代入にはならないため。
* プリミティブ型を再代入したい場合は`let`
* varはもう使わない