4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-07-13

varとletの違い

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

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

varの関数スコープを確認

JavaScript

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

結果(クリックして開く)
1
JavaScript

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

結果(クリックして開く)
2

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

varとletは箱の数が違う

JavaScript

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

結果(クリックして開く)
4
4
4

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

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

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

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

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を変更する
}

結果(クリックして開く)
1
22
3

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

letを二重にしてみた

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を変更する
}

結果(クリックして開く)
1 1
1 2
22 11
22 2
3 1
3 2

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

letの箱が増えない場合

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

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

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


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: 1

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

Q&A

Q1 箱のイメージを詳しく

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

箱のイメージ

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

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

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

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

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

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

for文の説明

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

  1. letとvarの違いがわかってからvarを使うようになった :sunglasses:

4
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?