JavaScriptを学び始めたばかりの方から、すでに現場で活躍しているエンジニアまで、避けて通れないのが「変数の宣言方法」です。
JavaScriptにはvar、let、constという3つのキーワードが存在しますが、これらを適切に使い分けることは、バグの少ない、メンテナンス性の高いコードを書くための第一歩となります。
かつてのJavaScriptではvarしか選択肢がありませんでしたが、ES6(ECMAScript 2015)の登場によってletとconstが加わり、開発の現場は劇的に変化しました。
本記事では、2026年現在のモダンな開発環境において、なぜこれらの使い分けが重要なのか、それぞれの仕組みや挙動の違いを詳しく解説していきます。
JavaScriptにおける変数宣言の歴史と進化
JavaScriptという言語が誕生した当初、変数を宣言する唯一の方法はvarでした。
しかし、JavaScriptがWebページに少しの動きをつけるための「スクリプト言語」から、複雑なWebアプリケーションを構築するための「プログラミング言語」へと進化する過程で、varの持つ柔軟性が逆に開発上のリスクとして認識されるようになりました。
var 時代に抱えていた課題
varは非常に自由度が高い一方で、大規模なプログラムでは意図しない変数の上書きや、スコープの曖昧さによるバグを引き起こしやすかったのです。
これを解決するために導入されたのが、より厳格で予測可能な挙動を持つletとconstです。
現代のプログラミングにおいては、「可能な限り不変(Immutable)を保ち、変更が必要な場合のみ最小限のスコープで可変(Mutable)を許容する」という考え方が主流となっています。
この方針に最も適しているのがconstであり、次いでlet、そしてvarは現在ではほぼ使用されない「非推奨」に近い扱いとなっています。
var 宣言:伝統的だが注意が必要な古い仕組み
まず、古くからあるvarの特徴を整理しましょう。
モダンな開発で使う機会は減っていますが、既存のコード(レガシーコード)を読み解く際や、なぜ新しい仕組みが必要だったのかを理解するために、その性質を知っておくことは重要です。
関数スコープと再宣言の許容
varの最大の特徴の一つは、「関数スコープ」であることです。
これは、変数が定義された関数全体で有効になるという性質です。
また、同じ名前の変数を何度でも再宣言できてしまうという特徴があります。
// varによる再宣言の例
var userName = "田中";
var userName = "佐藤"; // エラーにならず、上書きされる
console.log(userName);
function greet() {
var message = "こんにちは";
if (true) {
var message = "こんばんは"; // 同じ関数内なので上書きされる
}
console.log(message);
}
greet();
佐藤
こんばんは
このように、if文やfor文などのブロック({})の中で宣言しても、その外側の関数全体に影響を及ぼしてしまいます。
これは、エンジニアが意図せず変数を書き換えてしまう原因となります。
巻き上げ(Hoisting)による予期せぬ動作
varで宣言された変数は、コードの実行前に「スコープの先頭」に移動したかのように振る舞います。
これを「巻き上げ(Hoisting)」と呼びます。
// 巻き上げの例
console.log(point); // 宣言前なのにアクセスできる(undefinedが返る)
var point = 100;
console.log(point);
undefined
100
通常、宣言する前の変数にアクセスしようとすればエラーが出るのが自然ですが、varの場合はエラーにならずundefinedが返ってきます。
これはバグを特定しにくくする非常に危険な挙動です。
let と const:モダンな開発のスタンダード
ES6で導入されたletとconstは、varの欠点を補うために設計されました。
ブロックスコープによる安全性の向上
letとconstは「ブロックスコープ」を採用しています。
これは、if、for、while、あるいは単純な{}の囲いの中で宣言された変数は、その中だけでしか利用できないというルールです。
if (true) {
let blockVariable = "私はブロックの中だけです";
console.log(blockVariable);
}
// ここでアクセスしようとするとエラーになる
// console.log(blockVariable);
この仕組みにより、一時的に使いたい変数がプログラムの他の部分に影響を与える心配がなくなり、コードの堅牢性が高まりました。
再宣言の禁止
letとconstは、同じスコープ内での再宣言を禁止しています。
let score = 50;
// let score = 100; // SyntaxError: Identifier 'score' has already been declared
これにより、既に使われている変数名をうっかり再利用して値を壊してしまうミスを防ぐことができます。
一時的死区(TDZ)とエラー検知
letやconstにも巻き上げ自体は存在しますが、varとは異なり、宣言前にアクセスしようとすると厳格にエラー(ReferenceError)を発生させます。
この「宣言されているがアクセスできない領域」のことを「TDZ(Temporal Dead Zone / 一時的死区)」と呼びます。
// console.log(price); // ReferenceError
let price = 200;
プログラムが上から下へと実行される際、宣言より前に変数を使おうとしている箇所を即座に見つけられるため、デバッグが非常に容易になります。
var、let、const の決定的な違い(比較表)
ここで、3つの違いをわかりやすく表にまとめました。
| 特徴 | var | let | const |
|---|---|---|---|
| スコープ | 関数スコープ | ブロックスコープ | ブロックスコープ |
| 再代入 | 可能 | 可能 | 不可 |
| 再宣言 | 可能 | 不可 | 不可 |
| 巻き上げ | undefinedになる | エラー(TDZ) | エラー(TDZ) |
| 初期値の省略 | 可能 | 可能 | 必須 |
モダンな開発においては、「デフォルトは const、再代入が必要な場合のみ let、var は使わない」というルールが徹底されています。
なぜ現代のJavaScriptでは const を最優先するのか
多くのプログラミングガイドやコーディング規約では、constを最も優先的に使用することが推奨されています。
これには明確な理由があります。
再代入を制限することのメリット
constで宣言された変数は、後から値を書き換えることができません。
これは一見不便に思えるかもしれませんが、コードを読む側(自分やチームメンバー)にとっては大きな助けになります。
例えば、関数の冒頭でconst config = ...と宣言されていれば、その関数内のどこを読んでもconfigの中身が別のオブジェクトにすり替わっていないことが保証されます。
これにより、「変数の状態の変化」を追跡するコストが大幅に下がり、論理的なミスを減らすことができるのです。
オブジェクトや配列における const の挙動
初心者が最も混乱しやすいポイントが、constにおける「不変性」の範囲です。
constは「変数そのものへの再代入」を禁止しますが、「オブジェクトの中身の変更」までは禁止しません。
const user = {
name: "鈴木",
age: 25
};
// これはOK(オブジェクトのプロパティの変更)
user.age = 26;
// これはエラー(変数自体への再代入)
// user = { name: "佐藤", age: 30 };
console.log(user.age);
26
配列についても同様です。
pushメソッドなどで要素を追加することは可能ですが、配列そのものを別の配列に入れ替えることはできません。
この挙動を正しく理解しておくことは、JavaScriptにおける参照型の扱いをマスターする上で不可欠です。
実践的なコード例で見る使い分け
それでは、具体的な開発シーンでどのように使い分けが発生するかを見てみましょう。
ループ処理(for文)でのスコープの違い
特に顕著な違いが出るのが、繰り返し処理の中での変数宣言です。
// varの場合(意図しない挙動になりやすい)
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log("var i:", i);
}, 100);
}
// letの場合(期待通りの挙動)
for (let j = 0; j < 3; j++) {
setTimeout(function() {
console.log("let j:", j);
}, 100);
}
var i: 3
var i: 3
var i: 3
let j: 0
let j: 1
let j: 2
varは関数スコープであるため、ループが終わった後の最終的な値(3)をすべてのタイマーが参照してしまいます。
一方、letはループの各回ごとに新しいスコープを作成するため、その時の数値を正しく保持してくれます。
これだけでも、letがいかに強力で安全かがわかります。
グローバルスコープへの影響と汚染
ブラウザ環境において、関数の外でvarを宣言すると、それはグローバルオブジェクト(window)のプロパティになってしまいます。
var globalVar = "どこでも見えます";
let globalLet = "限定的です";
console.log(window.globalVar); // "どこでも見えます"
console.log(window.globalLet); // undefined
グローバルオブジェクトを汚染することは、他のライブラリやスクリプトとの名前の衝突を引き起こす原因となります。
モダンな開発ではモジュール(ES Modules)を使用することが一般的ですが、スコープを最小限に留めるletやconstの性質は常に有利に働きます。
2026年における最新のコーディング規約とベストプラクティス
2026年現在、JavaScriptのコーディングスタイルはさらに洗練されています。
以前は「まずはvarを覚える」という時代もありましたが、今は最初からletとconstを前提とした学習が標準です。
リンター(ESLint)の活用
多くの現場では、ESLintのような静的解析ツールを使用して、varの使用を自動的に検知し、エラーを出すように設定されています。
また、「一度も再代入されていないlet変数」があれば、「constに変更すべきである」という警告を出してくれます。
これにより、開発者のスキルセットに依存せず、プロジェクト全体のコード品質を一定以上に保つことが可能になっています。
「ツールに任せられる部分は任せ、人間はロジックに集中する」のが現代のスタイルです。
不変性を意識したプログラミング
ReactやVue.jsといったモダンフロントエンドフレームワークの普及により、「データを直接書き換えない」という不変性(Immutability)の重要性が増しています。
データを更新する際に、元のオブジェクトを書き換えるのではなく、新しいオブジェクトを作成して置き換える手法が取られます。
このとき、constを使っていれば、「この変数が指す先は変わらない」という安心感を持ってコードを記述できます。
const originalList = [1, 2, 3];
// originalListを直接変更せず、新しい配列を作る
const newList = [...originalList, 4];
console.log(originalList);
console.log(newList);
[1, 2, 3]
[1, 2, 3, 4]
まとめ
JavaScriptの変数宣言におけるvar、let、constの違いは、単なる書き方のバリエーションではなく、「プログラムの安全性と可読性をいかに高めるか」という進化の結果です。
最後に、これからの開発で守るべきガイドラインをまとめます。
- constをデフォルトにする:
変数を宣言する際は、まずconstを検討してください。値を変える必要がない、あるいはオブジェクトの中身を操作するだけであればconstが最適です。 - 再代入が必要な場合だけletを使う:
カウンタ変数や、条件によって値を入れ替える必要がある場合のみ、letを選択します。 - varは使用しない:
特別な理由(非常に古いブラウザへの対応が必要で、かつトランスパイラを使用できない環境など)がない限り、varを使うメリットはありません。
これらのルールを意識するだけで、あなたの書くJavaScriptコードは格段に読みやすく、そしてバグに強いものへと変わるはずです。
言語の仕様を正しく理解し、2026年のスタンダードにふさわしい美しいコードを目指しましょう。
