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