メインコンテンツまでスキップ

varはもう使わない

varは古い変数宣言の方法です。varにはいくつかの問題点がありました。それを解決するために、ES2015でletconstが導入されました。ここでは、varとその問題点を説明します。新たにコードを書く場合にはvar は使わずにletconstを使うことを推奨します。

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);
}

letconstでは、同名の変数宣言はエラーになるようになっています。

typescript
let x = 1;
let x = 2; // SyntaxError: Identifier 'x' has already been declared
const 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 declared
const 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

letconst はグローバルなスコープで定義されることはないため、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); // undefined
var greeting = "こんにちは";
// ↓ 巻き上げの影響で実際はこう実行される
var greeting;
console.log(greeting); // undefined
greeting = "こんにちは";
typescript
console.log(greeting); // undefined
var greeting = "こんにちは";
// ↓ 巻き上げの影響で実際はこう実行される
var greeting;
console.log(greeting); // undefined
greeting = "こんにちは";

varでの変数巻き上げでは参照エラーとならないため、意図せずに undefined の値を参照し予期せぬバグが発生する危険性があります。

letconstでは、宣言前の変数を参照するとReference Errorが発生します。

typescript
console.log(x); // ReferenceError
let x = 1;
console.log(y); // ReferenceError
const y = 2;
typescript
console.log(x); // ReferenceError
let x = 1;
console.log(y); // ReferenceError
const y = 2;

ただ、ここで注意すべきなのがletconstの場合でも変数の巻上げは発生しているという点です。では、なぜReference Errorが発生するのでしょうか?

varは変数の巻上げが発生したタイミングでundefined変数を初期化しているため、値の参照が可能となっていました。それに対してletconst は変数の巻上げが発生しても変数が評価されるまで変数は初期化されません。そのため、初期化されていない変数を参照するためReference Errorが発生しているのです。

次の例ではletconstで変数の巻き上げが発生しないならconsole.log(x)の評価のタイミングで関数の先頭で宣言されている var x = 1が参照されて1が出力されるはずです。しかし、実際はletで宣言された変数xがブロックスコープ内で初期化されていない状態で生成されるため、未初期化のxを参照してReference Errorが発生します。

typescript
function output() {
var x = 1;
{
console.log(x); // Reference Error
let x = 2;
}
}
output();
typescript
function output() {
var x = 1;
{
console.log(x); // Reference Error
let 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
}

letconstのスコープはブロックスコープです。次の例は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
}