var は使わない?
ECMAScript 2015でletとconstが導入されるまで、varを用いて変数を作成していましたが、
varにはいくつかの問題点がありました。古いコードとの互換性を保つために現在でも、使用することは可能ですが、新たにコードを書く場合にはvarは使わずにletかconstを使うことを強く推奨します。
varを使用することの問題点としては、下記のようなものがあります。
同じ変数を作成できてしまう
Section titled “同じ変数を作成できてしまう”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); // undefinedmyVar = "Hello"; // 代入はそのままの位置
// 比較:letの場合console.log(myLet); // 変数が作成されていないのでエラーになるlet myLet = "Hello";関数スコープ
Section titled “関数スコープ”varは「関数スコープ」を持ち、varで宣言された変数が関数全体で有効になります。
一方、letとconstは「ブロックスコープ」を持ち、letとconstで宣言された変数は{}で囲まれたブロック内でのみ有効です。
この違いにより、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)のプロパティになりますが、letやconstはなりません。意図しないwindowオブジェクトのプロパティ追加(グローバル汚染)を避けるためにも、let若しくはconstの使用が推奨されます。
// ブラウザ環境を想定var g = 1;let h = 2;const k = 3;
console.log(window.g); // 1(varはwindowに載る)console.log("h" in window); // falseconsole.log("k" in window); // falseこのように、varのfunctionスコープは直感的でなく、特にブロック内で変数を宣言したつもりが、意図せずより広い範囲で変数が有効になってしまい、バグの原因となります。