JavaScriptで変数を宣言する際、あなたは今もvar
を使っていませんか?ES6以降登場したlet
は、一見単純な構文変更のように見えますが、スコープ管理や再代入性、バグ回避など、実務に大きな影響を与える重要な構文です。この記事では、let
の基本とその実用上のメリット、注意点を整理し、より安全で読みやすいコード設計を目指すヒントをお届けします。
letの基本:ブロックスコープと再代入の特徴
let
は、JavaScriptのES6(2015年)で導入された新しい変数宣言キーワードで、以前から使われていたvar
に比べて、より直感的で安全なコードを書くことができます。このセクションでは、let
のスコープと再代入の特性を中心に、基本的な使い方とそのメリットを整理します。
let
のスコープは「ブロックスコープ」
✅ ブロックスコープとは?
let
で宣言された変数は、「{}」で囲まれたブロックの中だけで有効です。これにより、if文やfor文などの中で定義した変数が、外に漏れ出すことがありません。
例:
{
let x = 10;
console.log(x); // 10
}
console.log(x); // ReferenceError: x is not defined
この例のように、let
で宣言された変数x
は、ブロックの外では認識されず、スコープが明確に限定されています。
let
は再代入が可能
✅ 柔軟な変数管理が可能
let
で宣言した変数は、後から値を変更することができます。これは、状態が変化する処理において非常に有用です。
let counter = 0;
counter = counter + 1;
console.log(counter); // 1
ただし、再宣言は不可です。同一スコープ内で同じ変数名を再びlet
で宣言しようとすると、エラーになります。
let a = 5;
let a = 10; // SyntaxError: Identifier 'a' has already been declared
これにより、意図しない変数の上書きを防ぐことができ、コードの予測性と保守性が向上します。
varとの違いと使い分けのポイント
このセクションでは、let
と旧来のvar
の違いについて整理し、それぞれの特徴から実務での使い分けポイントを明確にします。特にスコープ、再宣言、ホイスティングの観点からの挙動の違いは、バグの原因にもなりやすいため、しっかり理解しておきましょう。
スコープの違い:var
は関数スコープ、let
はブロックスコープ
✅ var
の落とし穴
var
で宣言した変数は、関数全体がスコープになります。つまり、どこで宣言しても関数内ならどこでも参照できてしまい、スコープが緩すぎるという特徴があります。
function example() {
if (true) {
var x = 10;
}
console.log(x); // 10 - ブロック外でも参照可能
}
✅ let
はブロックに閉じたスコープ
一方でlet
は、そのブロック内でしか使えず、より厳密なスコープ管理が可能です。これは、より安全でバグの少ないコードに直結します。
再宣言:let
は同一スコープでの再宣言を禁止
✅ var
は再宣言OK、let
はNG
var
は同じスコープ内で何度でも宣言できますが、これが意図しない変数の上書きを引き起こす原因になります。let
ではこのような再宣言が禁止されており、より堅牢です。
var a = 1;
var a = 2; // OK
let b = 1;
let b = 2; // SyntaxError
ホイスティングの挙動:TDZ(Temporal Dead Zone)の存在
✅ var
は宣言が巻き上げられる(hoisting)
var
で宣言された変数は、スコープの先頭に「巻き上げ」られ、未定義状態(undefined
)で初期化されます。
console.log(x); // undefined
var x = 5;
✅ let
はホイスティングされるが、TDZによってエラーになる
let
も技術的にはホイスティングされますが、TDZによって、宣言前の参照が禁止されています。
console.log(y); // ReferenceError: Cannot access 'y' before initialization
let y = 5;
この違いは、初学者が見落としがちなポイントであり、実務でのバグの発生源になりがちです。let
の方が、明確な変数のライフサイクルを持たせることができます。
サンプルで理解するletのスコープ挙動
コードを通じて違いを見ることで、理論的な説明がより明確に理解できます。このセクションでは、let
とvar
のスコープ挙動の違いが顕著に表れるケースとして、ループと非同期処理の組み合わせを取り上げます。
ループ内でのスコープの違い
✅ let
を使った場合:各ループごとに独立したスコープが作られる
JavaScriptの例:
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// 出力:0, 1, 2(それぞれのスコープでiが異なる)
このように、let
で定義された変数i
はループの各回で独立したスコープを持つため、非同期処理(setTimeout)が正しいi
の値を保持してくれます。これは開発中のバグ回避に非常に役立ちます。
✅ var
を使った場合:1つのスコープしか持たない
JavaScriptの例:
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// 出力:3, 3, 3(すべて同じiが参照される)
こちらは、var
で宣言された変数i
がループの外側でも同じスコープに存在するため、非同期処理が呼び出されるときには、すでにループが終了しており、i
の値が3になっているという状況です。
実務での応用例:イベントリスナーとループ
✅ let
を使うことで、イベントハンドラが正しい要素に対応
const buttons = document.querySelectorAll("button");
for (let i = 0; i < buttons.length; i++) {
buttons[i].addEventListener("click", () => {
console.log(`ボタン${i}がクリックされました`);
});
}
このように、let
を使えば、各イベントリスナーが独自のインデックスi
を保持できます。これをvar
で書いた場合、すべてのイベントで同じi
が出力されるというバグに繋がるため注意が必要です。
letのメリット・デメリット
ここでは、let
を使うことで得られる利点と、逆に注意が必要な点を整理します。すべてのツールには長所と短所があるように、let
にも用途に応じた使い分けが求められます。
メリット
✅ スコープが明確で予測可能なコードになる
let
はブロックスコープを持つため、変数が意図せず外部に影響を及ぼすリスクが低くなります。これにより、保守性や再利用性が高いコード設計が可能になります。
✅ 意図しない再宣言を防止できる
let
は同一スコープ内での再宣言を許可しないため、誤って変数を上書きするようなバグを未然に防げます。コードの安全性が高まる点で非常に重要な機能です。
✅ ループや非同期処理との親和性が高い
先ほど紹介したように、let
はループの各イテレーションごとにスコープが分離されるため、非同期処理との組み合わせで想定通りの動作をしやすくなります。
✅ var
のホイスティングによるバグを回避できる
TDZ(Temporal Dead Zone)の存在によって、宣言前に変数へアクセスするとエラーになるため、未定義の変数を参照してしまうようなバグを回避できます。
デメリット
⚠️ 古い環境ではサポートされていないことがある
現在では主要なブラウザすべてでlet
はサポートされていますが、極端に古い環境(たとえばIE10以前など)では動作しません。古いシステムを対象にする場合はトランスパイルやポリフィルが必要です。
⚠️ スコープが細かくなることで変数シャドウイングの可能性
ブロックスコープによって意図しないシャドウイング(同名の変数が外側スコープと衝突すること)が起こることもあります。これは一見してコードの挙動がわかりにくくなる原因にもなるため、命名規則や構造に注意が必要です。
let value = "外側";
{
let value = "内側";
console.log(value); // "内側"
}
console.log(value); // "外側"
このように、スコープの境界に対する意識がないと、バグの原因になりかねません。
実務での活用ポイントとベストプラクティス
let
は便利で安全な変数宣言手段ですが、実務ではただ使えばいいというわけではありません。このセクションでは、プロジェクトでの運用において意識したい「使い分け」や「設計のコツ」を整理します。
新しいコードではvar
を使わないのが基本
✅ 新規開発では原則としてlet
かconst
を使用
モダンなJavaScript開発では、var
を避けるのが一般的なベストプラクティスです。これは、var
のスコープやホイスティングの挙動が原因で生まれる不具合を回避するためです。現在の開発環境では、ES6以降の構文が標準化されているため、let
とconst
を前提とした設計が推奨されます。
const
とlet
の使い分けを明確にする
✅ 基本はconst
、再代入が必要な場合のみlet
を使用
不変の変数はconst
で宣言し、状態が変化する必要がある場合にだけlet
を使います。これにより、どの変数が変更されうるのかが明確になり、意図しない副作用を防げます。
const MAX_RETRIES = 5;
let retryCount = 0;
このように使い分けることで、変数の「意図」をコードから読み取りやすくなります。
スコープ設計を意識した変数配置
✅ ループ・条件分岐内で使う変数はそのブロック内で宣言する
スコープが広いと、それだけバグの温床になります。let
を使う場合でも、なるべく変数を使う場所の近くで宣言することで、コードの可読性と安全性が向上します。
if (user.isLoggedIn) {
let welcomeMessage = `ようこそ、${user.name}さん`;
showAlert(welcomeMessage);
}
このように、**「変数を最小スコープで使う」**という意識を持つことが重要です。
チーム開発ではリンター設定と組み合わせる
✅ ESLintなどの静的解析ツールを活用して、var
の使用を禁止
プロジェクトにESLintやPrettierを導入することで、コーディング規約に沿った変数宣言(let
やconst
の使い分け)を強制できます。特にno-var
ルールを設定することで、var
の使用を事前に防げます。
// .eslintrcの例
"rules": {
"no-var": "error",
"prefer-const": "warn"
}
このようにツールとルールを併用することで、属人的な判断を排除し、チーム全体のコード品質を保つことができます。
まとめ
let
の登場は、JavaScriptにおける変数宣言のパラダイムを大きく変えるものでした。単なる構文の違いではなく、スコープ設計・再代入性・安全性といった、コードの根本的な品質に直結する改善をもたらしています。
本記事では、以下のポイントを中心に解説しました:
let
はブロックスコープを持ち、意図した範囲で変数管理ができるvar
との違い(スコープ・再宣言・ホイスティングの挙動)を正しく理解することが重要- 実際のコードサンプルを通じて、非同期処理との相性の良さを確認
let
のメリットとともに、意図しないシャドウイングなど注意点も存在する- 実務では
const
との使い分けや、最小スコープでの宣言、静的解析ツールの活用がベストプラクティス
これからのJavaScript開発では、let
やconst
を前提としたコードが当たり前となります。特に、保守性・安全性・可読性を重視するモダンな開発環境では、var
は過去のものと考えて差し支えありません。
今こそ、自分のコードベースを見直し、「いつ」「なぜ」let
を使うのかを明確にする設計指針を取り入れる好機です。より信頼性の高いコードを目指して、一歩前に進んでみてはいかがでしょうか。
コメント