varはもう使わない
varは古い変数宣言の方法です。varにはいくつかの問題点がありました。それを解決するために、ES2015でletとconstが導入されました。ここでは、varとその問題点を説明します。新たにコードを書く場合にはvar は使わずにletとconstを使うことを推奨します。
varの変数宣言#
var次のように書くことで変数を宣言できます。
javascriptvar name = "taro";
javascriptvar name = "taro";
初期値を省略した変数宣言もできます。その場合の変数値はundefinedです。
javascriptvar name;
javascriptvar name;
varの問題点#
var による変数宣言には気をつけるべき挙動が何点か存在します。
同名の変数宣言#
varの変数宣言では同じ変数名で宣言をした場合にエラーとならずに、後から宣言された変数が有効となります。これは思いがけず既存の変数を書き換えてしましい、意図しない結果を出力する可能性があります。
javascriptfunction test() {var x = 1;var x = 2;console.log(x);}
javascriptfunction test() {var x = 1;var x = 2;console.log(x);}
letとconstでは、同名の変数宣言はエラーになるようになっています。
typescriptlet x = 1;let x = 2; // SyntaxError: Identifier 'x' has already been declaredconst y = 1;const y = 2; // SyntaxError: Identifier 'y' has already been declared
typescriptlet x = 1;let x = 2; // SyntaxError: Identifier 'x' has already been declaredconst y = 1;const y = 2; // SyntaxError: Identifier 'y' has already been declared
グローバル変数の上書き#
varはグローバル変数として定義されたときに、window オブジェクトのプロパティとして定義されるため、既存のプロパティを上書きする危険性があります。
たとえば、ブラウザ上で innerWidth 変数をグローバル変数として定義してしまうと、標準API の window.innerWidth が上書きされるため、ブラウザの幅を変更しても常に同じ値が返ってくるようになってしまいます。
javascriptvar innerWidth = 10;console.log(window.innerWidth); // 10
javascriptvar innerWidth = 10;console.log(window.innerWidth); // 10
let や const はグローバルなスコープで定義されることはないため、window オブジェクトのプロパティを不用意に上書きする心配はありません。
typescriptconst innerWidth = 10;console.log(window.innerWidth); // 500
typescriptconst innerWidth = 10;console.log(window.innerWidth); // 500
変数の巻上げ#
JavaScriptで宣言された変数はスコープの先頭で変数が生成されます。これは変数の巻き上げと呼ばれています。varで宣言された変数は、スコープの先頭で生成されてundefinedで値が初期化されます。次の例では greeting変数への参照はエラーとならずに undefined となります。
typescriptconsole.log(greeting); // undefinedvar greeting = "こんにちは";// ↓ 巻き上げの影響で実際はこう実行されるvar greeting;console.log(greeting); // undefinedgreeting = "こんにちは";
typescriptconsole.log(greeting); // undefinedvar greeting = "こんにちは";// ↓ 巻き上げの影響で実際はこう実行されるvar greeting;console.log(greeting); // undefinedgreeting = "こんにちは";
varでの変数巻き上げでは参照エラーとならないため、意図せずに undefined の値を参照し予期せぬバグが発生する危険性があります。
letとconstでは、宣言前の変数を参照するとReference Errorが発生します。
typescriptconsole.log(x); // ReferenceErrorlet x = 1;console.log(y); // ReferenceErrorconst y = 2;
typescriptconsole.log(x); // ReferenceErrorlet x = 1;console.log(y); // ReferenceErrorconst y = 2;
ただ、ここで注意すべきなのがletとconstの場合でも変数の巻上げは発生しているという点です。では、なぜReference Errorが発生するのでしょうか?
varは変数の巻上げが発生したタイミングでundefinedで変数を初期化しているため、値の参照が可能となっていました。それに対してletとconst は変数の巻上げが発生しても変数が評価されるまで変数は初期化されません。そのため、初期化されていない変数を参照するためReference Errorが発生しているのです。
次の例ではletやconstで変数の巻き上げが発生しないならconsole.log(x)の評価のタイミングで関数の先頭で宣言されている var x = 1が参照されて1が出力されるはずです。しかし、実際はletで宣言された変数xがブロックスコープ内で初期化されていない状態で生成されるため、未初期化のxを参照してReference Errorが発生します。
typescriptfunction output() {var x = 1;{console.log(x); // Reference Errorlet x = 2;}}output();
typescriptfunction output() {var x = 1;{console.log(x); // Reference Errorlet x = 2;}}output();
スコープ#
JavaScript ではvarで宣言された変数のスコープは関数となるため、{} の中で変数宣言をしても最初に定義した変数xは上書きされます。
typescriptfunction print() {var x = 1;if (true) {var x = 2;console.log(x); // 2}console.log(x); // 2}
typescriptfunction print() {var x = 1;if (true) {var x = 2;console.log(x); // 2}console.log(x); // 2}
letとconstのスコープはブロックスコープです。次の例はvarでは変数xが上書きされていましたが、ここではブロックスコープ内で異なる変数として別々に定義されています。
typescriptfunction print() {const x = 1;if (true) {const x = 2;console.log(x); // 2}console.log(x); // 1}
typescriptfunction print() {const x = 1;if (true) {const x = 2;console.log(x); // 2}console.log(x); // 1}