Javascriptのvar、let、constの違いを調べてみた

プログラミングには変数がつきものです。
各言語によって宣言方法は異なりますが、Javascriptの場合の変数宣言は長らく「var」だけでOKでした。
ところが最近、「var」以外に「let」と「const」という変数宣言が増えました。
いったい何が違うの?
と疑問に思ったため、いつものごとく調べてみました。
ざっくりと変数の違いを表にしてみた。
var、let、constの違いについて調べると、次のような表がでてきました。
var | let | const | |
(1)再代入 | 可能 | 可能 | 不可 |
(2)再宣言 | 可能 | 不可 | 不可 |
(3)スコープ | 関数スコープ | ブロックスコープ | ブロックスコープ |
(4)ホイスティング | される | される(TDZあり) | される(TDZあり) |
表だけ見るとわかったような気になりますが、私はさっぱりわかりませんでした。
再代入や、再宣言は何となくイメージが付きますが、スコープ、ホイスティングっていったい何?
なんだか余計にわからなくなってきました。
ということで一つずつ調べてみました。
(1)再代入の違い

再代入とは、変数の中身に新たな内容をセットすることです。
具体的には以下のような内容です。
var a = 0;
a = 1;
変数を宣言し、初期値を入れた後に違う値をセットすること。
それを再代入といいます。
varとletは再代入OKですが、constは再代入できません。
次のようになります。
var a = 1;
let b = 1;
const c = 1;
a = 2; // var 変数は再代入OK
b = 2; // let 変数は再代入OK
c = 2; // TypeError const 変数は再代入すると致命的エラーになります。
constは1宣言に対し、1回しか値をセットできないようです。
初期値などによいのかもしれません。
(2)再宣言の違い

再宣言とは、宣言した後に再び宣言することです。
具体的には以下のようなことです。
var a = 0;
var a = 1;
あまり意味のないことですが、宣言を2回しています。
varは再宣言はできますが、letとconstは再宣言するとエラーになります。
次のようになります。
var a = 0;
let b = 0;
const c = 0;
var a = 1; // var の再宣言はOK
let b = 1; // SyntaxError let の再宣言は文法エラー
const c = 1; // SyntaxError const の再宣言は文法エラー
(3)スコープの違い

スコープの違いといわれてもよくわかりません。
スコープとは、変数の影響範囲を示します。
一般的には外部変数、内部変数というイメージですが、Javascriptの場合もっと細かく定義されています。
意味合い的には以下のような意味になります。
sample();
function sample()
{
for(count=0 ; count < 3 ; count++)
{
var a = count;
}
console.log(a); // var の場合同一関数内であれば参照できる
}
console.log(a); // 関数の外だとエラーになる。
var は内部変数、外部変数的な動作をしますが、let constの場合は少し違います。
sample();
function sample()
{
for(count=0 ; count < 3 ; count++)
{
var a = count;
let b = count;
const c = count;
}
console.log(a); // var の場合同一関数内であれば参照できる
console.log(b); // ReferenceError let の場合階層が異なるとエラーになる。
console.log(c); // ReferenceError const の場合階層が異なるとエラーになる。
}
console.log(a); // ReferenceError var の場合関数の外だとエラーになる。
console.log(b); // ReferenceError let の場合関数の外もとエラーになる。
console.log(c); // ReferenceError const の場合階関数の外もとエラーになる。
letとconstは、同一関数内であっても、同一階層より上位だとエラーになります。
下位階層だとエラーになりません。
ここで疑問:forの中の const c = count;は再宣言にならないの?
サンプルを書いていて疑問に感じたことがあります。
constは、再代入や再宣言は文法エラーになります。
なのに、forでループしている中に毎回値をセットしてよいのでしょうか?
調べてみると、forの{}内はループごとに新しいスコープ(ブロック)が生成されるため、その都度新規宣言していることになります。constは、別スコープ(ブロック)であれば、重複宣言にならないため、ループ処理内で、毎回値をセットしても大丈夫でした。
ただし、以下のような書き方だと致命的エラーになります。
function sample()
{
const a = 0;
for(cnt=0 ; cnt < 3 ; cnt++)
{
a = cnt;
}
}
これはconstの宣言1回に対して、const変数への代入を何度も行っているためです。
varやletならばエラーにならないです。
ループの中で宣言はOKなのに、なんとも不思議な仕様です。
(4)ホイスティングの違い

ホイスティングって何?
調べれば調べるほど意味が分かりませんでした。
実際には使うことはほどんどないと思われますが、varとの違いという意味では重要な意味があります。
console.log(a);
var a = 1;
// aは後から宣言されていますが、記述上文法エラーになりません。
この例では、宣言する前に変数を使用しています。
ホイスティングとは、宣言する前に使用することです。
変数の場合、ほとんど意味がないですが、関数の場合だと重要な意味があります。
例えば以下のケースはよくあると思います。
sample();
function sample()
{
関数の中身
}
関数宣言する前に、関数をコールする。
プログラムではよくあることです。
ですが宣言前にエラーにならないのは、ホイスティングしているからと言えます。
では、letとconstの場合はどうなるのでしょうか?
それがこちらです。
console.log(a); // var の場合エラーにならない。
var a;
console.log(a); // 宣言後はエラーにならない
console.log(b); // let の場合ReferenceErrorになる。
let b;
console.log(b); // 宣言後はエラーにならない
console.log(c); // constの場合SyntaxErrorになる。
const c; // constの場合初期化をしない宣言だけの場合はエラーになる。
console.log(c); // 宣言自体がエラーなので宣言後もエラー
宣言前に変数を参照する場合、var以外はエラーになります。
ですが、letとconstでエラーの毛色が異なります。
letはReferenceErrorになるのに、constはSyntaxErrorです。
宣言前に変数を参照すると、なぜこのような違いが出るのでしょうか?
letの場合はTDZエラー
TDZってよく意味の分からない言葉ですが、変数の宣言が終わるまでの期間というような意味です。
ホイスティング(宣言前の参照)をすると、TDZ内アクセス(変数の宣言が終わる前のアクセス)となります。
letがReferenceErrorになるのは、変数宣言が終わる前に変数参照をしているからです。
varの場合はエラーになりませんが、letの場合だとエラーになります。
ちなみに以下の場合はエラーになりません。
先に宣言しているため、TDZ後アクセスになるためです。
let b;
console.log(b);
constの場合は初期化エラー
letの場合はTDZによるエラーでしたが、const変数の場合は、SyntaxError(文法エラー)になります。
これは、const変数の場合は初期化が必須なためです。
つまり以下のように、宣言と同時に初期化をすればOKです。
const c;
console.log(c);
各変数のまとめ

各変数をまとめると次のようになります。
- varは何でもかんでもOK
- 普段使うならletがよい
- constは初期値(設定値)指定用
Jqueryで使う場合はどうなるのか?
実際に使用する場合、Jqueryを交えて使うことがほとんどです。
ということで、Jqueryの書式で検証してみました。
var _a;
let _b;
const _c=3;
$(function()
{
_a = 10;
_b = 20;
_c = 30; // TypeErrorになる。(再代入によるエラー)
sample();
sample02();
});
function sample()
{
// 関数外で宣言した変数の参照はOK
console.log(_a);
console.log(_b);
console.log(_c);
}
function sample02()
{
// 関数内で再宣言するのは、スコープが異なるためOK
var _a = “text1";
let _b = “text2";
const _c = “text3";
console.log(_a);
console.log(_b);
console.log(_c);
}
最上位で宣言した変数は、引数渡しをしなくても下層まで参照できました。
意外と、varをletに書き換えるだけでよさそうです。
結局のところ正しく動けばOKだけどlet constを使えばより良くなる。

Javascriptにletやconstという変数指定が増えたのは、意図しない記述ミスをエラーとするためです。
どういうことかというと、すでに使っている変数を忘れて、別の意味で同じ名称の変数を宣言してしまうと、論理的エラー(バグ)がかなり高い確率で発生します。
これを明示的にわかりやすくするために生み出されたようです。
変数名の管理や、論理エラーを精密に管理できるならば、varのままで問題ありません。
ですが、letやconstを使えば、論理エラーをさらに減らせるということになります。
結局のところ、不具合なく動作させられば、変数の型は何でもよいのです。
ですが、letやconstを使えば、うっかりミスを減らせるため、より良くなります。
今までletやconstって何だろう?
と思っていましたが、知ることでより向上できました。