JavaScriptのプログラミングにおいて、かつて変数を宣言する唯一の手段だったvarは、現代のフロントエンド開発やサーバーサイド開発(Node.js)において「過去の遺物」となりつつあります。
2015年に登場したECMAScript 2015(ES6)以降、letとconstが導入されたことで、varを使用するメリットはほぼ消失しました。
この記事では、なぜ現在の開発現場でvarの使用が推奨されないのか、その具体的な理由と、letおよびconstとの決定的な違いについて詳しく解説します。
安全で読みやすいコードを書くためのベストプラクティスを学び、バグの少ないJavaScript開発を実現しましょう。
JavaScriptにおける変数宣言の歴史的背景
JavaScriptが誕生した当初から、変数の宣言にはvarが使われてきました。
当時のJavaScriptは、Webページに少し動きをつけるための補助的な言語という位置づけであり、言語設計も比較的シンプルで柔軟なものでした。
しかし、Webアプリケーションが高度化し、コードの規模が膨大になるにつれ、varの持つ「柔軟すぎる性質」が、予期せぬバグの温床となることが明らかになってきました。
この問題を解決するために登場したのがletとconstです。
これらはモダンなプログラミング言語に近いスコープの概念を持ち、開発者が意図しない動作を防ぐように設計されています。
2026年現在の開発環境においては、特別な理由がない限りvarを記述することはありません。
varを使わない最大の理由:スコープの挙動
varが抱える最大の問題は、そのスコープ(変数の有効範囲)にあります。
関数スコープとブロックスコープの違い
varは「関数スコープ」を持ちます。
これは、関数の中で宣言された変数は、その関数内のどこからでも参照できるというものです。
一方、letやconstは「ブロックスコープ」を持ちます。
ブロックスコープは、if文やfor文などの{}(中括弧)で囲まれた範囲内でのみ有効です。
以下のコードを見てみましょう。
// varの場合
if (true) {
var x = "Hello var";
}
console.log(x); // ブロックの外側からもアクセスできてしまう
// letの場合
if (true) {
let y = "Hello let";
}
// console.log(y); // ここで呼び出すとReferenceErrorが発生する
Hello var
varで宣言された変数xは、if文のブロックを超えて外部からアクセス可能です。
これにより、本来その場だけで使い切りたかった変数が、外部の処理に影響を与えてしまうリスクが生じます。
forループ内での意図しない動作
特に問題となるのが、forループ内での動作です。
以前のJavaScriptでは、ループ内のカウンタ変数にvarを使っていたため、クロージャや非同期処理を組み合わせた際に意図しない結果を招くことが多くありました。
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log("varのインデックス:", i);
}, 100);
}
varのインデックス: 3
varのインデックス: 3
varのインデックス: 3
期待していた結果は「0, 1, 2」ですが、varは関数スコープであるため、ループが終わる頃には全ての関数が同じi(最終値の3)を参照してしまいます。
これをletに書き換えるだけで、各ループごとに新しい変数がバインドされるため、期待通りの動作になります。
二重宣言の許可によるリスク
varのもう一つの危険な特徴は、同じ変数名で何度でも宣言できてしまう点です。
意図しない上書き
大規模な開発において、複数のファイルや長い関数がある場合、すでに使用されている変数名を誤って再宣言してしまうことがあります。
var userName = "田中";
// 数百行のコードがあったとする
var userName = "佐藤"; // エラーにならず、上書きされる
console.log(userName);
佐藤
letやconstを使用している場合、同じスコープ内での再宣言は構文エラー(SyntaxError)となります。
これにより、誤って既存の変数を破壊してしまうミスを未然に防ぐことができます。
ホイスティング(巻き上げ)による混乱
JavaScriptには「ホイスティング(巻き上げ)」という仕組みがあります。
これは、変数宣言がスコープの先頭に移動したかのように振る舞う動作のことです。
varにおけるホイスティング
varで変数を宣言すると、宣言の前にその変数にアクセスしてもエラーにならず、undefinedが返されます。
console.log(message); // エラーにならず undefined と表示される
var message = "こんにちは";
undefined
これはコードの読み手に混乱を与えます。
変数が定義される前に使われているにもかかわらず、プログラムが動き続けてしまうため、バグの発見が遅れる原因になります。
letとconstにおけるTDZ(Temporal Dead Zone)
letやconstも内部的にはホイスティングされていますが、宣言より前の位置からアクセスしようとするとReferenceErrorが発生します。
このアクセス不能な期間を「TDZ(Temporal Dead Zone:一時的死区)」と呼びます。
// console.log(greeting); // ReferenceError: Cannot access 'greeting' before initialization
let greeting = "こんばんは";
「宣言する前に使うことはできない」という当たり前のルールが徹底されることで、実行時の安全性が飛躍的に高まります。
var・let・constの比較まとめ
それぞれの違いを整理すると以下のようになります。
現在の開発では「原則としてconstを使い、再代入が必要な場合のみletを使う」というルールが一般的です。
| 特徴 | var | let | const |
|---|---|---|---|
| スコープ | 関数スコープ | ブロックスコープ | ブロックスコープ |
| 再代入 | 可能 | 可能 | 不可能 |
| 再宣言 | 可能 | 不可能 | 不可能 |
| ホイスティングの挙動 | undefinedで初期化される | エラーになる (TDZ) | エラーになる (TDZ) |
| グローバルオブジェクトへの登録 | される (window.varName) | されない | されない |
グローバルオブジェクトの汚染問題
ブラウザ環境において、グローバルスコープ(関数の外側)でvarを使用して変数を宣言すると、その変数はwindowオブジェクトのプロパティとして登録されます。
var globalValue = 100;
console.log(window.globalValue); // 100
100
これは非常に危険な挙動です。
意図せず既存のwindowオブジェクトのプロパティを上書きしてしまったり、ライブラリ間で名前が衝突したりする原因になります。
対照的に、letやconstはグローバルスコープで宣言してもwindowオブジェクトには登録されません。
グローバルな環境を汚さないという点でも、varを避けるべき理由があります。
安全なコードを書くための実践的ガイドライン
2026年のモダンな開発においては、以下のルールを徹底することが推奨されています。
1. デフォルトは常にconstを選択する
多くの変数は、一度代入した後に値を変更する必要がありません。
再代入を禁止することで、その変数が指し示す値がプログラムの途中で変わらないことが保証され、コードの推論が容易になります。
const TAX_RATE = 0.1;
const userList = ["Alice", "Bob"];
// userList = []; // これはエラーになる
userList.push("Charlie"); // 配列の要素変更は可能(再代入ではないため)
2. 再代入が必要な場合のみletを使用する
ループのカウンタ変数や、条件によって値を更新していく変数にはletを使用します。
let score = 0;
if (isWinner) {
score += 10;
}
3. varは「使わない」と心に決める
レガシーなシステム(10年以上前のプロジェクトなど)の保守を行っている場合を除き、varを新しく書く理由はありません。
もしプロジェクトにvarが残っている場合は、少しずつletやconstへリファクタリングすることを検討しましょう。
ただし、スコープの挙動が変わるため、単純な置換ではなく動作確認を伴う修正が必要です。
ESLintによる強制的なチェック
チーム開発においては、個人の意識に頼るのではなく、静的解析ツールを活用してvarの使用を禁止するのが一般的です。
ESLintを導入し、no-varルールを有効にすることで、コード内にvarが紛れ込むのを自動的に防ぐことができます。
{
"rules": {
"no-var": "error",
"prefer-const": "error"
}
}
このような設定を行うことで、varを使用した瞬間にエディタ上でエラーが表示されるようになり、プロジェクトのコード品質を一定以上に保つことが可能になります。
パフォーマンスとメモリ管理の観点
技術的に言えば、var、let、constの間に劇的な実行速度の差はありません。
現代のJavaScriptエンジン(V8など)は非常に高度に最適化されているため、変数宣言の使い分けによる速度差を気にする必要はほぼありません。
むしろ重要なのはメモリ管理と予測可能性です。
ブロックスコープを持つletやconstは、不要になったタイミングでガベージコレクション(GC)の対象になりやすいため、メモリ効率の面でも有利に働く場合があります。
また、コードの可読性が上がることで開発工数が削減されるという、人間にとってのパフォーマンス向上こそが最大のメリットと言えるでしょう。
まとめ
JavaScriptでvarを使わない理由は、単なる流行ではなく、言語としての安全性と堅牢性を高めるための必然的な進化にあります。
- スコープの制御:ブロックスコープによる限定的な有効範囲
- 再宣言の禁止:名前の衝突によるバグの防止
- TDZの導入:宣言前の変数使用による混乱の回避
- イミュータビリティ:
constによる意図しない再代入の防止
これらのメリットを享受することで、デバッグの時間を減らし、よりロジックの本質に集中したコーディングが可能になります。
2026年の今日、私たちが書くコードは、後から読み返す自分自身やチームメンバーにとって「意図が明確で安全なもの」であるべきです。
もし現在、無意識にvarを使っている箇所があれば、今日からすべてconst(またはlet)に置き換えてみてください。
その一歩が、プロフェッショナルなJavaScriptエンジニアとしての第一歩となるはずです。
