JavaScriptで変数を宣言するとき、letとconstは共にES6で導入されたキーワードですが、実際の使い方や挙動は大きく異なります。何を使うべきか悩む初心者も多く、特にスコープや再代入、ホイスティングなどの違いは理解しておかないとバグの原因になります。本記事では、JavaScript 変数 let const 違いを中心に、最新情報を踏まえて具体例やベストプラクティスとともに分かりやすく解説します。
目次
JavaScript 変数 let const 違い:基本的な特徴と違い
まずはletとconstがどのような性質を持ち、何が共通で何が異なるのかを整理します。これを理解することで、その後のスコープやホイスティング、エラーの発生原因などが把握しやすくなります。
スコープ(有効範囲)の違い
letとconstはどちらもブロックスコープを持ちます。中括弧{}で囲まれたblock内でのみ変数が有効で、外側からはアクセスできません。これは従来のvar(関数スコープ/グローバルスコープ)と大きく異なる点です。
例として、if文やfor文の中でlet/constを使うと、その中で宣言された変数はその中だけで有効で、外からはundefinedもしくは参照できないという動作になります。これにより、意図しない値の上書きやスコープ外参照のバグを防ぐことができます。
再代入と再宣言の許可/禁止
letは再代入を許可します。すなわちletで宣言した変数には宣言後に別の値を設定できますが、constは一度設定した値への再代入ができません。再宣言(同じスコープ内で同じ名前の宣言)もconstとletでは禁止されており、varのみが同一スコープ内で再宣言を許可します。
ただしconstで宣言したオブジェクトや配列は、参照先そのもの(オブジェクト/配列のプロパティや要素)は変更可能です。再代入(新しいオブジェクトをその変数に割り当てること)が禁止されるだけです。
ホイスティングとTemporal Dead Zone(TDZ)
JavaScriptでは宣言文がスコープの先頭に“持ち上げられる”ホイスティングの概念があります。しかしletとconstの場合、宣言はホイスティングされるものの、実際に初期化されるまではその変数にアクセスできず、TDZ(Temporal Dead Zone)と呼ばれる期間が存在します。
このTDZの期間中にlet/const変数にアクセスするとReferenceErrorが発生します。varの場合はホイスティング時にundefinedで初期化されるため、宣言前アクセスでundefinedが返るだけでエラーにはなりません。
宣言時の初期化の必須性
constで変数を宣言する際は必ず初期化(値を設定)しなければなりません。初期化を行わないとSyntaxErrorになります。letは初期化を後に行っても構いません。つまり宣言と初期化が同時でなくても許されます。
スコープとホイスティングの実際の挙動
それでは、スコープとホイスティングがどのように動作するかを実例で確認します。特にTDZの発生タイミングやvar/let/constの違いを理解すると、コード設計がより安全になります。
ブロックスコープの例
例えば次のようなコード:
if (true) {
let x = 10;
const y = 20;
console.log(x, y);
}
console.log(x, y);
この例では、ブロックスコープ内で宣言されたxとyはブロック外からはアクセスできず、外部でconsole.logを実行するとReferenceErrorが発生します。スコープ外での変数汚染を防ぐためにlet/constは非常に有用です。
ホイスティングとTDZの実例
次のようなコードを考えてください:
console.log(a); // undefined
var a = 5;
console.log(b); // ReferenceError
let b = 10;
console.log(c); // ReferenceError
const c = 15;
このように、varは宣言前のアクセスでundefinedを返しますが、let/constはTDZ中とみなされてエラーになります。これによって誤った値の使用を避けることができます。
関数パラメータとネストスコープでの影響
関数やネストしたブロック内で同名の変数をlet/constで再宣言すると、それぞれ独立したスコープを持つため、外側の変数と混同することはありません。varではこうした混乱が起きやすいため、モジュール設計や複雑なロジックではlet/constの方が信頼できます。
constの詳細ルールと誤解しやすいポイント
constには初心者が陥りがちな誤解や特殊な挙動があります。それらを知っておくことで、予期せぬエラーを防ぎコードの品質を上げることができます。
バインドの不変性と値の可変性
constは変数そのもののバインド(参照)を不変にしますが、バインス先のオブジェクトや配列の内容までは保護しません。つまり、constで宣言したオブジェクトのプロパティは変更可能です。完全なイミュータブル(不変)を求める場合はObject.freezeなどを使う必要があります。
初期化なしの宣言禁止
constでは宣言時に初期化が必須です。初期化を省略すると構文エラーが発生します。これにより、未定義状態の変数バインドを防ぎ、バグを事前に検出しやすくします。letでは初期化なし宣言が可能で、その場合の初期値はundefinedです。
再宣言・再代入の禁止エラー
同一スコープ内でのletまたはconstでの再宣言はSyntaxErrorになります。constでは再代入も禁止されており、値を変更しようとするとTypeErrorになります。このようなエラーは開発時に即時にフィードバックを得られるため、バグが早期発見しやすい構造になっています。
用途ごとの使い分けとベストプラクティス
ここでは、どのような状況でletを使い、どのような場合にconstを選ぶとよいかの指針を紹介します。最新の開発スタイルを反映しています。
constをデフォルトとして使う
コードを書くときはまずconstを使い、再代入が必要な場合にのみletに切り替えるアプローチが推奨されます。こうすることで、不変なバインドを増やし予期しない変化を抑制できます。またリファクタリング時にもどこが本当に可変かを把握しやすくなります。
ループ、カウンタ、状態変数など可変が必要なときのletの使用
for文のカウンタや状態管理用の変数(フラグ、カウンタなど)は値を変える必要があるのでletが適しています。可変が必要なパラメータや中間値など、コードの動きに応じて変わる部分でletを使います。
varの避けるべき理由と代替行動
varは関数スコープやグローバルスコープの作用範囲が広く、ホイスティングや再宣言の挙動が直感と異なるためバグの原因になります。ES6以降ではvarを使うべきケースはほとんどなく、letとconstで代替可能なほか、Lintツールでvarの使用を禁止する設定をするプロジェクトも多く見られます。
パフォーマンスと互換性の観点からの注意点
letとconstは仕様上varよりも安全性が高いですが、パフォーマンスやレガシーブラウザ対応、コードの互換性など実務上無視できない要素もあります。
現代ブラウザでのパフォーマンス差
ほとんどの場合、letとconstのパフォーマンス差は微小であり、実質的には意識する必要はありません。JavaScriptエンジンはこれらの使い方を最適化しており、開発者が優先すべきは可読性とバグ低減です。極端なループや大量の数値演算では影響が出ることもありますが、それも最適化の範囲で改善可能です。
古い環境やトランスパイルの必要性
古いブラウザ(例Internet Explorerや一部旧バージョンのモバイルブラウザなど)ではlet/constのサポートが不十分なことがあり、トランスパイラー(Babelなど)を使ってES5互換に変換するケースがあります。ライブラリやプロジェクト設定でトランスパイルのパイプラインを整えておくことが現場での安心につながります。
リント・静的解析ツールを併用する
コード規約やLintツール(例えばESLint)のno-varやprefer-const等のルールを設定することで、let/const/varの使い分けを自動でチェックできます。このようなツールを導入することでチーム内のスタイル統一とバグ予防に効果があります。
まとめ
letとconstにはスコープ、再代入の可否、宣言時の初期化必須性、ホイスティングとTDZといった複数の違いがあります。これらを理解することで、安全で予測可能なコードを書けるようになります。まずはconstをデフォルトとし、本当に値が変わる場合のみletを使う習慣をつけることが重要です。
varは機能的には今でも使えますが、スコープ汚染やホイスティングによる予期せぬ挙動を引き起こしやすいため、モダンな開発環境ではlet/constで代替するのがベストです。
この違いを押さえておくことで、バグの混入が減り、コードの意図が読みやすく、保守性の高い実装ができるようになります。
コメント