実はletのメモリ効率が悪かった


# varとletの違い

物心ついたときからvarを使わずletを使えばいいとだけ学んでいたのでvarとletの違いをあまり気にしたことがなかった。

letとvarの違いについて以下の通り試して理解を深めたという話。
クイズ形式にしたらおもしろいかと思ってプログラムの結果を隠してみた。


## varの関数スコープを確認

```javascript:JavaScript

{
 var i = 2;
 { var i = 1; }
 console.log(i);
}

```

<details><summary>結果(クリックして開く)</summary><div>

```
1
```

</div></details>

```javascript:JavaScript

{
 var i = 2;
 (function () { var i = 1; })();
 console.log(i);
}

```

<details><summary>結果(クリックして開く)</summary><div>

```
2
```

</div></details>

上の２つからわかることはvarが関数スコープということだ。


## varとletは箱の数が違う

```javascript:JavaScript

{
 for (var i = 1; i < 4; i++) {
  setTimeout(function () { console.log(i) }, 100);
 }
}

```

<details><summary>結果(クリックして開く)</summary><div>

```
4
4
4
```

</div></details>

この結果からわかることはvarの箱は一箇所。

`var i`を`let i`に変えると希望通り1 2 3を返す。
最初、letのとき`function(){}`が`let i`をbindするのかと思ったがそうではなく、letは箱がいっぱいあるようだ。
このことは次の実験からわかる。


## letの値を外から変更してみた

letはブロックスコープという。
ブロックスコープのletの値を外から変更してみたらどうなるか試してみた。
letの値を変更する関数をreturnしてみる。

```javascript:JavaScript

{
 var g = (function () {
  for (let i = 1; i < 4; i++) {
   setTimeout(function () { console.log(i) }, 100);
   if (i === 2) { var f = function (ii) { i = ii; } }
  }
  return f; // i===2のlet iを変更する関数を返す
 })();
 g(22); // i===2のlet iを変更する
}

```

<details><summary>結果(クリックして開く)</summary><div>

```
1
22
3
```

</div></details>

この結果からわかることは`let i`の箱は3箇所。


## letを二重にしてみた

```javascript:JavaScript

{
 var g = (function () {
  for (let i = 1; i < 4; i++) {
   for (let j = 1; j < 3; j++) {
    setTimeout(function () { console.log(i, j) }, 100);
    if (i === 2 && j === 1) { var f = function (ii, jj) { i = ii; j = jj; } }
   }
  }
  return f; // i===2, j===1のlet i, let jを変更する関数を返す
 })();
 g(22, 11); // i===2, j===1のlet i, let jを変更する
}

```

<details><summary>結果(クリックして開く)</summary><div>

```
1 1
1 2
22 11
22 2
3 1
3 2

```
</div></details>

この結果からわかることは`let i`の箱は3箇所、`let j`の箱は6箇所。
forの段数が増えるごとにletの箱の数が乗数的に増えていく。


# letの箱が増えない場合

letの箱が増えるのはletをforの初期化式に書いたときのみ。

```javascript:JavaScript
{
 let i = 1; // ここにletを書く
 for (; i < 4; i++) {
  setTimeout(function () { console.log(i) }, 100);
 }
}
```

このプログラムは4を3回出力する。
letをforの初期化式に記述するとletの箱が増えるが、letをforの初期化式に記述しないとletの箱は増えない。

<br>

```javascript:JavaScript
{
 for (var i = 1; i < 4; i++) {
  var j = i;
  let k = i;
  setTimeout(function () { console.log(i, j, k) }, 100);
 }
}
```

このプログラムは

```
4 3 1
4 3 2
4 3 3
```

を出力する。
つまり、どういうことか？

var変数は関数スコープ（function scope）を持ち、関数の先頭に箱が作られる。

let変数はブロックスコープ（block scope）を持ち、ブロック（{}の括弧）ごとに箱が作られる。


# 結論

varは箱が1箇所なのでvarのほうがletよりもメモリ効率が良い（しかし不具合を出しやすい）。

~~そのような人はいないと思うが~~、メモリ効率を良くするためにvarを使う人はいるのだろうか？ :thinking: [^メモリ効率]

varの箱は1箇所、letの箱はいっぱい。
varを使っているときは複数の文脈からvarを使っていないか気をつけよう。


# Q&A

## Q1 箱のイメージを詳しく

> letとvarを箱の数の違いで表現されていますが、もう少し詳しく、箱のというイメージで捉えるためのご説明をお願いできませんか？

![箱のイメージ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311399/a4e6af86-319c-d187-c2bb-a35e1fd182f6.png)

比喩でなく説明すると、箱はメモリ上の場所です。

letはブロックスコープなので、forの繰り返しに対して別の箱が用意されるようです。
varはforの繰り返しに対して同じ１つの箱を使い回しします。

varのときクリックイベントで１つの箱を使い回そうとしたときループが進んで別の値に変わってしまっていたというのがよくある不具合です。

letの欠点はメモリの無駄遣いをしていることで、letの長所は不具合が起きにくいことです。


## Q2 forループ後にi=4となるのはなぜ？

> varとletは箱の数が違うに関してのところでconsoleに４が表示されるのはなぜでしょうか？
> i < 4なので最大でもiは3までの数値になるような気がするのですが。
> 処理は３回、回っているのにiはなぜ4と表示されているのでしょうか？

![for文の説明](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311399/b2f33e78-a385-68a5-9006-3f347edb6ddf.png)

他のteratailの質問で***for文の制御フロー***を解説するために作成した図です。
変数`i=3`のとき終了条件`i<4`が真になり、命令文本体が実行されます。
次に「増減」の`i++`が実行され、`i=4`となり終了条件`i<4`が偽になり、for文が終了します。
forループ後に`i=4`となります。

[^メモリ効率]: letとvarの違いがわかってからvarを使うようになった :sunglasses: