コンテンツにスキップ

var は使わない?

ECMAScript 2015でletconstが導入されるまで、varを用いて変数を作成していましたが、
varにはいくつかの問題点がありました。古いコードとの互換性を保つために現在でも、使用することは可能ですが、新たにコードを書く場合にはvarは使わずにletconstを使うことを強く推奨します。

varを使用することの問題点としては、下記のようなものがあります。

varは同じ変数を何度でも作成できてしまいます。これにより、既存の変数を誤って上書きしてしまう可能性があります。

// varの場合
var userName = "田中";
var userName = "山田"; // エラーにならず、単に上書きされる
// letの場合
let userName = "田中";
let userName = "山田"; // エラーになる
// constの場合
const userName = "田中";
const userName = "山田"; // エラーになる

予期せぬバグを引き起こす巻き上げ(ホイスティング)

Section titled “予期せぬバグを引き起こす巻き上げ(ホイスティング)”

varで作成された変数は、変数を宣言する前にその変数を使用してもエラーになってくれません。これは巻き上げ(ホイスティング)と呼ばれています。

// 問題のある例
console.log(myVar); // undefined (エラーにならない)
var myVar = "Hello";
// 上記は実際には以下のように解釈されている
var myVar; // 宣言が巻き上げられる
console.log(myVar); // undefined
myVar = "Hello"; // 代入はそのままの位置
// 比較:letの場合
console.log(myLet); // 変数が作成されていないのでエラーになる
let myLet = "Hello";

varは「関数スコープ」を持ち、varで宣言された変数が関数全体で有効になります。 一方、letconstは「ブロックスコープ」を持ち、letconstで宣言された変数は{}で囲まれたブロック内でのみ有効です。

この違いにより、varは予期しない動作を引き起こすことがあります。

function demo() {
if (true) {
var a = 1;
let b = 2;
const c = 3;
}
console.log(a); // 1(varは関数スコープなので参照可能)
console.log(b); // ReferenceError(letはブロックスコープ)
console.log(c); // ReferenceError(constもブロックスコープ)
}
demo();

この違いはループでもバグを生みます。 varはループごとの新しい束縛を作らないため、varで宣言した変数がすべて同じ値を参照してしまいます。letは各反復で新しい束縛を作るため、期待どおりの結果になります。

const fns1 = [];
for (var i = 0; i < 3; i++) {
fns1.push(() => i);
}
console.log(fns1.map((fn) => fn())); // [3, 3, 3](すべて最終値)
const fns2 = [];
for (let j = 0; j < 3; j++) {
fns2.push(() => j);
}
console.log(fns2.map((fn) => fn())); // [0, 1, 2](各反復ごとの値)

また、ブラウザ環境のグローバルスコープでは、varで宣言した変数はグローバルオブジェクト(window)のプロパティになりますが、letconstはなりません。意図しないwindowオブジェクトのプロパティ追加(グローバル汚染)を避けるためにも、let若しくはconstの使用が推奨されます。

// ブラウザ環境を想定
var g = 1;
let h = 2;
const k = 3;
console.log(window.g); // 1(varはwindowに載る)
console.log("h" in window); // false
console.log("k" in window); // false

このように、varのfunctionスコープは直感的でなく、特にブロック内で変数を宣言したつもりが、意図せずより広い範囲で変数が有効になってしまい、バグの原因となります。