こんにちは、かずん(@kazoonLab)です。
前回の記事では、「JavaScriptのスコープ」についてお話ししました。
今回は、スコープと密接にかかわる、「var / let / const」についてです!

この記事を読むと次のことがわかるよ!
- var / let / constそれぞれの使い方
- かずん流 宣言のベストなやり方
はじめに
そもそも変数とは何か、という話に関しては今回は触れません。今回は、「var / let / const」それぞれの振る舞いが、どのように異なるかを見ていきたいと思います。
変数の基礎的な部分が分からなければ、このリンクが参考になるかと思います。
それぞれの使い方
varとは
varは、古くから変数宣言に用いられてきました。
varは、以下の特徴を持ちます。
・初期化不要
・関数スコープである
・巻き上げが発生する
・再定義できる
・再代入できる
スコープについては以下の記事にて触れていますので、そちらをご覧ください。
blog.mimipen.net
それでは、例を見てみましょう。
// グローバルな変数を定義 var global;(1) global = 'global'; function globalFunc () { // 関数内でローカルな変数を定義 var local = 'local';(2) console.log(global); // --> global console.log(local); // --> local }; console.log(global); // --> global console.log(local); // --> (Error)(3) // グローバル変数に再代入する global = 'global2'; console.log(global); // --> global2 // グローバル変数を再定義する var global = 'global3'; console.log(global); // --> global3(4) // 巻き上げを確認する(5) console.log(newGlobal); // --> undefined var newGlobal = 'newGlobal'; console.log(newGlobal); // --> newGlobal;
変数の初期化とは、変数宣言と同時に値を代入する行為を指します(上記②のように)
(1)では変数宣言のみ行い、値の代入はしていません。この場合、変数globalはデフォルト値undefinedで初期化されます。
関数globalFunc()内で変数localが宣言されている(2)ため、この関数の外からは変数localは参照できません。(3)
(4)のように、varで宣言された変数は再定義できます(できてしまいます)。変数を多重に宣言することは、意図しないコードの挙動(バグ)の原因となり得ます。
また、varには「巻き上げ」という仕様があります。これは、「あるスコープの中で宣言された変数は、スコープのどの位置で宣言されようとも、スコープの戦闘で宣言されたことになる」というものです。
(5)以下の挙動は、以下のコードと同じになります。
var newGlobal; console.log(newGlobal); // --> undefined(6) var newGlobal = 'newGlobal'; console.log(newGlobal); // --> newGlobal;
(6)でundefinedな値を参照していますがエラーになっていません。これも、意図しない挙動といえるでしょう。
では、次にletを見ていきましょう。
letとは
letは、varの不安定な箇所を補うべく誕生しました。
letは、以下の特徴を持ちます。
・初期化不要
・ブロックスコープである
・巻き上げが発生するが、エラーになる
・再定義できない
・再代入できる
例を見てみましょう。
// 初期化せず宣言 let global;(1) // 代入前の変数呼び出しはエラーになる console.log(global); // --> (error)(2) global = 'global';(3)
// 初期化して宣言 let global2 = 'global2'; console.log(global2); // --> global2 global = 'global'; if (true) { // ブロック(if文)の中でローカル変数を宣言 let local = 'local' console.log(local); // --> local } // ブロックの外からは、ローカル変数は参照できない(4) console.log(local); // --> (error);
let global3 = 'global3'; // 再定義はNG let global3 = 'global4'; // --> (error) console.log(global3);
let global4 = 'global4'; // 再代入はOK global4 = 'global5'; console.log(global4); // --> global5
varと同様、初期化せず宣言した変数はundefinedで初期化されます(1)。ただし、(3)で初期化する前に変数の呼び出しをするとエラーになります(2)。ここがletとvarで異なるポイントです。
厳密にいえば、巻き上げは発生しているのですが、代入前の参照はエラーになるという仕様となっています。
(4)のように、ブロック内で宣言した変数は外から参照できません。このため、ブロック外での意図しない更新を防ぐことができます。
最後に、最も厳密な宣言「const」を見ていきましょう。
constとは
constは、letよりも厳密な変数宣言を行うべく誕生しました。
constは、以下の特徴を持ちます。
・初期化が必要
・ブロックスコープである
・巻き上げが発生しない
・再定義できない
・再代入できない(例外有り)
例を見てみましょう。
// 初期化しない変数宣言はエラー const global; // --> (error)
const global = 'global'; // 再定義はエラー const global = 'global2'; // --> (error) // 再代入もエラー global = 'global3'; // --> (error)
const global = 'global'; if (true) { const local = 'local'; console.log(local); // --> local } console.log(local); // --> (error)
constで宣言される変数は、宣言と同時にに初期化しなければならず、また再代入・再定義できません。
またブロックスコープであるため、ブロック外からの参照もできません。巻き上げについても、宣言と
一見すると不便なように見えますが、実はとてもありがたい存在なのです。その理由を考えてみましょう。
ある変数がconstで宣言されていて、かつソースコードにエラーが発生していない場合、
「この変数は絶対にほかの場所では使われていない。また、この変数は常に最初の値のままである」
と考えることができるでしょう。つまり、ソースコードを書く人は、意図しない参照を防ぐ(しようとするとエラー)ことができます。
一方。ソースコードを読む人は、ブロック内のconst変数は常に変わらない(*)と信じて読むことができます。
まとめると、constを利用することで、書き手・読み手双方の心理的負担、また費やす時間を削減することができるのです。
(*)プリミティブな値(数字、文字列、...)は再代入できませんが、オブジェクトや配列の中の値の更新はできてしまいます。
const obj = { key: 'value' }; // 中の値の更新はエラーにならない obj.key = 'value2'; // --> (ok, { key: 'value2' }) // オブジェクトそのものの更新はエラーになる obj = { key: 'value2'} // --> (error) // 配列も同じ const arr = [0, 1, 2]; arr[0] = 1; // --> (ok, [1, 1, 2]) arr = [3, 4, 5]; // --> (error)
まとめ
ここまで、var, let, constそれぞれの特徴について見てきました。
あくまで私の意見ですが、コーディング時にきちんとエラーを出してほしい、また読むときになるべく苦にならないように、
「原則宣言はconstで行い、再代入の必要があればletを使う」
のが良いのではないかと思います。varには別れを告げてしまいましょう。
この記事を読んでくださった方のソースコードが、よりよくなることを願ってやみません。
最後までご覧いただきありがとうございます。
この記事がいいなっと思ったら読者登録をお願いいたします♪
【ツイッターでは、仕事のことなどをつぶやいています】
技術者の方ともっと繋がりたいっす!お気軽にフォローもお願いします!
かなきち
かずん