JavaScriptは、ウェブ開発において最も重要なプログラミング言語の一つとして進化を続けています。
かつてはvarのみで行われていた変数宣言も、ECMAScript 2015(ES6)の登場以降、letやconstが導入され、開発の現場ではこれらを適切に使い分けることが品質の高いコードを書くための必須条件となりました。
現代のJavaScript開発においては、変数のスコープ、再代入の可否、そしてメモリ管理の観点から、最適な宣言方法を選択することが求められます。
本記事では、初学者から中級者までを対象に、これら3つの宣言方法の違いと、実務で役立つ現代的な書き方について詳しく解説します。
JavaScriptにおける変数宣言の歴史的背景と現代の基準
JavaScriptの初期段階から存在するvarによる宣言は、その柔軟性の反面、意図しないバグを引き起こしやすいという性質を持っていました。
プログラムの規模が拡大し、複雑なアプリケーションが構築されるようになるにつれ、より厳格で予測可能な変数管理が必要とされるようになったのです。
現在、モダンな開発環境においてvarを使用することはほとんどありません。その代わりに、再代入を許さないconstを基本とし、再代入が必要な場合のみletを使用するというスタイルが標準となっています。
このアプローチは「イミュータビリティ(不変性)」を重視するプログラミングパラダイムとも親和性が高く、コードの可読性と保守性を飛躍的に向上させます。
2026年現在のフロントエンド・バックエンド(Node.js/Deno/Bun)開発においても、この原則は変わっていません。
むしろ、静的解析ツール(ESLintなど)やTypeScriptの普及により、変数宣言の厳格さはより一層重視されるようになっています。
const:再代入を禁止する宣言の基本
現代のJavaScriptコーディングにおいて、最も優先的に使用すべきなのがconstです。constは「Constant(定数)」の略であり、一度値を代入すると、その変数に対して別の値を再代入することができません。
constの基本的な性質
constで宣言された変数は、宣言と同時に初期化を行う必要があります。
宣言だけを行って後から代入することはできません。
// 正しい使い方
const taxRate = 0.1;
console.log(taxRate);
// 誤った使い方(エラーになります)
// const discountRate;
// discountRate = 0.2;
0.1
このように、再代入を禁止することで、その変数がプログラムの実行過程で予期せず書き換えられるリスクを排除できます。
これは、コードを読み進める開発者にとって「この変数は最後まで同じ役割を果たす」という保証になり、認知負荷を軽減する効果があります。
オブジェクトと配列におけるconstの挙動
初心者が混同しやすいポイントとして、constで宣言されたオブジェクトや配列の「中身」の変更があります。
constが禁止するのは「変数そのものへの再代入」であり、参照先のオブジェクトのプロパティや配列の要素を変更することは可能です。
const user = {
id: 1,
name: "田中"
};
// プロパティの変更は可能
user.name = "佐藤";
console.log(user.name);
// オブジェクト自体の再代入は不可
// user = { id: 2, name: "鈴木" }; // TypeErrorが発生
佐藤
もし、オブジェクトの中身自体も変更できないようにしたい場合は、Object.freeze()などのメソッドを併用する必要があります。
しかし、基本的な設計思想としては、再代入の必要がないものはすべてconstで定義するという方針を徹底することが、堅牢なプログラムへの第一歩となります。
let:再代入が必要な場面での適切な利用
letは、値の書き換えが必要な変数を宣言するために使用されます。
ループのカウンタ変数や、条件分岐によって代入する値が変わる場合などが主な用途です。
letの活用シーン
例えば、数値を順番に加算していく処理や、フラグの状態を管理する場合にはletが適しています。
let totalScore = 0;
const points = [10, 20, 30];
for (let i = 0; i < points.length; i++) {
totalScore += points[i];
}
console.log("合計スコア:", totalScore);
合計スコア: 60
この例では、ループカウンタのiと、合計値を保持するtotalScoreが再代入を繰り返すため、letで宣言されています。
letとconstの使い分けの基準
実務では、以下の優先順位で宣言を選択します。
- 原則としてすべてconstで書けないかを検討する。
- どうしても再代入が必要な場合のみ
letを採用する。 varは使用しない。
多くのロジックは、配列メソッド(mapやfilter、reduceなど)を活用することで、letを使わずにconstだけで記述することが可能です。
不必要なletを減らすことで、状態の変化が追いやすくなり、バグの混入を防ぐことができます。
var:なぜ現代のコードでは避けるべきなのか
varはJavaScriptの誕生当初からある宣言方法ですが、現代の開発では推奨されません。
その最大の理由は、「意図しないスコープの汚染」と「再宣言の許可」という設計上の問題にあります。
varが抱える問題点
- 関数スコープであること:
varは、if文やfor文などのブロックを突き抜けて有効になります。 - 再宣言が可能であること:同じ変数名を二度宣言してもエラーにならないため、既存の変数を上書きしてしまう危険があります。
- 巻き上げ(Hoisting)の影響:宣言前に変数にアクセスしてもエラーにならず、
undefinedが返されるという不可解な挙動を示します。
var message = "Hello";
var message = "World"; // 再宣言が可能(エラーにならない)
if (true) {
var globalLike = "I am visible outside";
}
console.log(globalLike); // ブロックの外からアクセスできてしまう
I am visible outside
このような挙動は、大規模な開発において変数の管理を極めて困難にします。
特に、誤って同名の変数を再定義してしまった際にエラーが出ないことは、デバッグの時間を大幅に増大させる原因となります。
スコープ(Scope)の理解と変数の有効範囲
変数宣言を正しく使い分けるためには、「スコープ」という概念の理解が不可欠です。
スコープとは、ある変数が「どこから参照できるか」という有効範囲のことを指します。
ブロックスコープ(let / const)
letとconstは「ブロックスコープ」を採用しています。
ブロックとは、波括弧{}で囲まれた範囲のことです。
if文、for文、while文、あるいは単なる{}の中に閉じ込められた変数は、その外側からはアクセスできません。
{
const internalValue = "秘密のデータ";
console.log(internalValue);
}
// ここでアクセスしようとすると ReferenceError になる
// console.log(internalValue);
秘密のデータ
この仕組みにより、一時的にしか使わない変数がグローバルな範囲や関数全体を汚染することを防げます。
関数スコープ(var)
一方で、varは「関数スコープ」を持ちます。
これは、関数の中で宣言された変数は関数全体で有効ですが、if文などのブロックではスコープが作られないことを意味します。
| 特徴 | const | let | var |
|---|---|---|---|
| スコープ | ブロック | ブロック | 関数 |
| 再代入 | 不可 | 可能 | 可能 |
| 再宣言 | 不可 | 不可 | 可能 |
| 巻き上げ | あり(TDZあり) | あり(TDZあり) | あり(undefined) |
ホイスティング(巻き上げ)とTDZ(一時的死区)
JavaScriptには、コードの実行前に変数宣言をメモリ上に配置する「ホイスティング(巻き上げ)」という仕組みがあります。
varの巻き上げ
varの場合、宣言より前でアクセスしてもエラーにならず、値がundefinedとして扱われます。
console.log(greeting); // undefined
var greeting = "こんにちは";
これは直感に反する挙動であり、エラーにならないことが逆にバグの発見を遅らせます。
let / constの巻き上げとTDZ
letやconstも内部的には巻き上げが行われますが、「Temporal Dead Zone(TDZ:一時的死区)」という仕組みにより、宣言される前にアクセスしようとすると厳格にエラー(ReferenceError)を発生させます。
// console.log(alertMessage); // ReferenceError: Cannot access 'alertMessage' before initialization
let alertMessage = "注意!";
この制約があるおかげで、開発者は「変数は使う前に宣言する」という当たり前の習慣を強制され、結果として実行時の安全性が保障されるようになっています。
変数宣言におけるベストプラクティスと命名規則
コードの可読性を高めるためには、宣言の方法だけでなく、どのように名前を付けるか、どのように配置するかといった作法も重要です。
基本はcamelCase
JavaScriptの変数名は、伝統的に「キャメルケース(camelCase)」で記述します。
1単語目は小文字で始め、2単語目以降の先頭を大文字にする形式です。
const userName = "Alice";
let currentLoginAttempts = 3;
意味のある名前を付ける
aやtempといった抽象的な名前は避け、その変数が何を保持しているのかが一目でわかる名前を付けましょう。
「名詞」としての役割を意識するのがコツです。
- 悪い例:
const data = 10; - 良い例:
const maxRetryCount = 10;
宣言の場所をまとめる
変数の宣言は、その変数が最初に使用される場所の近くで行うのが一般的です。
かつてのvarの時代は、関数の先頭ですべての変数を宣言する習慣もありましたが、現代のブロックスコープ中心の設計では、必要なときに、必要な最小限のスコープで宣言することが推奨されます。
マジックナンバーを避ける
コード内に突然現れる具体的な数値は「マジックナンバー」と呼ばれ、保守を困難にします。
これらはconstを使って定数として定義しましょう。
// 避けるべき例
if (status === 1) { ... }
// 推奨される例
const STATUS_ACTIVE = 1;
if (status === STATUS_ACTIVE) { ... }
慣習として、アプリケーション全体で共有される「真の定数」には、大文字のアンダースコア区切り(SCREAMING_SNAKE_CASE)が使われることもあります。
分割代入による洗練された宣言
現代的な書き方として欠かせないのが「分割代入(Destructuring Assignment)」です。
オブジェクトや配列から特定の値を抽出して、個別の変数として宣言する強力な構文です。
const config = {
apiEndpoint: "https://api.example.com",
timeout: 5000,
retries: 3
};
// 分割代入による宣言
const { apiEndpoint, timeout } = config;
console.log(apiEndpoint); // https://api.example.com
console.log(timeout); // 5000
この手法を使うことで、複数のプロパティを一行でスッキリと変数化でき、コードの重複を減らすことができます。
メモリ管理とガベージコレクションの観点
変数宣言は、ブラウザのメモリ管理とも密接に関係しています。
不要になった変数は、JavaScriptエンジンの「ガベージコレクション(GC)」によって自動的にメモリから解放されます。
しかし、広すぎるスコープ(グローバルスコープなど)で変数を宣言してしまうと、その変数はプログラムが終了するまでメモリに残り続けます。
可能な限りconstやletを使い、ブロック内に変数を閉じ込めることで、GCが効率的に働ける環境を整えることができます。
これは、メモリ消費を抑え、パフォーマンスの高いアプリケーションを実現するための重要なポイントです。
まとめ
JavaScriptにおける変数宣言は、単なる「値の器」を定義する作業ではありません。
それは、そのデータがプログラムの中でどのような性質を持ち、どの範囲まで影響を及ぼすのかを決定する重要な設計行為です。
現代のJavaScript開発において守るべきルールはシンプルです。
- 基本はconstを選ぶ:再代入を避け、コードの予測可能性を高める。
- 再代入が必要ならletを使う:ループや状態変化が必要な場面に限定する。
- varは決して使わない:予期せぬ挙動やスコープの混乱を避ける。
- 適切なスコープを意識する:グローバル汚染を防ぎ、メモリ効率を最適化する。
これらのルールを徹底することで、バグが少なく、他の開発者にとっても読みやすい高品質なコードを記述できるようになります。
2026年という時代においても、この基本的な規律は変わることなく、プロフェッショナルな開発の基盤であり続けています。
まずは、自分の書いているコードの中でletをconstに置き換えられないか見直すことから始めてみましょう。
それだけで、あなたのプログラムはより洗練されたものに進化するはずです。
