varとletの違い
物心ついたときからvarを使わずletを使えばいいとだけ学んでいたのでvarとletの違いをあまり気にしたことがなかった。
letとvarの違いについて以下の通り試して理解を深めたという話。
クイズ形式にしたらおもしろいかと思ってプログラムの結果を隠してみた。
varの関数スコープを確認
{
var i = 2;
{ var i = 1; }
console.log(i);
}
結果(クリックして開く)
1
{
var i = 2;
(function () { var i = 1; })();
console.log(i);
}
結果(クリックして開く)
2
上の2つからわかることはvarが関数スコープということだ。
varとletは箱の数が違う
{
for (var i = 1; i < 4; i++) {
setTimeout(function () { console.log(i) }, 100);
}
}
結果(クリックして開く)
4
4
4
この結果からわかることはvarの箱は一箇所。
var i
をlet i
に変えると希望通り1 2 3を返す。
最初、letのときfunction(){}
がlet i
をbindするのかと思ったがそうではなく、letは箱がいっぱいあるようだ。
このことは次の実験からわかる。
letの値を外から変更してみた
letはブロックスコープという。
ブロックスコープのletの値を外から変更してみたらどうなるか試してみた。
letの値を変更する関数をreturnしてみる。
{
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を二重にしてみた
{
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の初期化式に書いたときのみ。
{
let i = 1; // ここにletを書く
for (; i < 4; i++) {
setTimeout(function () { console.log(i) }, 100);
}
}
このプログラムは4を3回出力する。
letをforの初期化式に記述するとletの箱が増えるが、letをforの初期化式に記述しないとletの箱は増えない。
{
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を使う人はいるのだろうか? 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と表示されているのでしょうか?
他のteratailの質問でfor文の制御フローを解説するために作成した図です。
変数i=3
のとき終了条件i<4
が真になり、命令文本体が実行されます。
次に「増減」のi++
が実行され、i=4
となり終了条件i<4
が偽になり、for文が終了します。
forループ後にi=4
となります。
-
letとvarの違いがわかってからvarを使うようになった ↩