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

varはもう使わない

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

varの変数宣言

varは次のように書くことで変数を宣言できます。

js
var name = "taro";
js
var name = "taro";

初期値を省略した変数宣言もできます。その場合の変数値はundefinedです。

js
var name;
js
var name;

varの問題点

varによる変数宣言には気をつけるべき挙動が何点か存在します。

同名の変数宣言

varの変数宣言では同じ変数名で宣言をした場合にエラーとならずに、後から宣言された変数が有効となります。これは思いがけず既存の変数を書き換えてしまい、意図しない結果を出力する可能性があります。

js
function test() {
var x = 1;
var x = 2;
console.log(x);
}
js
function test() {
var x = 1;
var x = 2;
console.log(x);
}

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

ts
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
ts
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が上書きされるため、ブラウザの幅を変更しても常に同じ値が返ってくるようになってしまいます。

js
var innerWidth = 10;
console.log(window.innerWidth);
10
js
var innerWidth = 10;
console.log(window.innerWidth);
10

letconstはグローバルなスコープで定義されることはないため、windowオブジェクトのプロパティを不用意に上書きする心配はありません。

ts
const innerWidth = 10;
console.log(window.innerWidth);
500
ts
const innerWidth = 10;
console.log(window.innerWidth);
500

📄️ 変数のスコープ

スコープ(scope)とは、変数がどこから参照できるかを定めた変数の有効範囲のことです。JavaScriptには大きく分けてグローバルスコープとローカルスコープの2つがあります。

変数の巻き上げ

JavaScriptで宣言された変数はスコープの先頭で変数が生成されます。これは変数の巻き上げと呼ばれています。varで宣言された変数は、スコープの先頭で生成されてundefinedで値が初期化されます。次の例ではgreeting変数への参照はエラーとならずにundefinedとなります。

ts
console.log(greeting);
undefined
var greeting = "こんにちは";
 
// ↓ 巻き上げの影響で実際はこう実行される
 
var greeting;
console.log(greeting);
greeting = "こんにちは";
undefined
ts
console.log(greeting);
undefined
var greeting = "こんにちは";
 
// ↓ 巻き上げの影響で実際はこう実行される
 
var greeting;
console.log(greeting);
greeting = "こんにちは";
undefined

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

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

ts
console.log(x);
Block-scoped variable 'x' used before its declaration.
Variable 'x' is used before being assigned.
2448
2454
Block-scoped variable 'x' used before its declaration.
Variable 'x' is used before being assigned.
let x = 1;
 
console.log(y);
Block-scoped variable 'y' used before its declaration.
Variable 'y' is used before being assigned.
2448
2454
Block-scoped variable 'y' used before its declaration.
Variable 'y' is used before being assigned.
const y = 2;
ts
console.log(x);
Block-scoped variable 'x' used before its declaration.
Variable 'x' is used before being assigned.
2448
2454
Block-scoped variable 'x' used before its declaration.
Variable 'x' is used before being assigned.
let x = 1;
 
console.log(y);
Block-scoped variable 'y' used before its declaration.
Variable 'y' is used before being assigned.
2448
2454
Block-scoped variable 'y' used before its declaration.
Variable 'y' is used before being assigned.
const y = 2;

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

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

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

ts
function output() {
var x = 1;
{
console.log(x);
Block-scoped variable 'x' used before its declaration.
Variable 'x' is used before being assigned.
2448
2454
Block-scoped variable 'x' used before its declaration.
Variable 'x' is used before being assigned.
let x = 2;
}
}
 
output();
ts
function output() {
var x = 1;
{
console.log(x);
Block-scoped variable 'x' used before its declaration.
Variable 'x' is used before being assigned.
2448
2454
Block-scoped variable 'x' used before its declaration.
Variable 'x' is used before being assigned.
let x = 2;
}
}
 
output();

スコープ

JavaScript ではvarで宣言された変数のスコープは関数となるため、{}の中で変数宣言をしても最初に定義した変数xは上書きされます。

ts
function print() {
var x = 1;
if (true) {
var x = 2;
console.log(x);
2
}
console.log(x);
2
}
ts
function print() {
var x = 1;
if (true) {
var x = 2;
console.log(x);
2
}
console.log(x);
2
}

letconstのスコープはブロックスコープです。次の例はvarでは変数xが上書きされていましたが、ここではブロックスコープ内で異なる変数として別々に定義されています。

ts
function print() {
const x = 1;
if (true) {
const x = 2;
console.log(x);
2
}
console.log(x);
1
}
ts
function print() {
const x = 1;
if (true) {
const x = 2;
console.log(x);
2
}
console.log(x);
1
}