【ITニュース解説】JavaScript let vs var: A Beginner's Guide to Block Scoping
2025年09月13日に「Dev.to」が公開したITニュース「JavaScript let vs var: A Beginner's Guide to Block Scoping」について初心者にもわかりやすく解説しています。
ITニュース概要
JavaScriptの古い変数宣言`var`は関数スコープや巻き上げにより予期せぬバグの原因となった。ES6で導入された`let`はブロックスコープで変数の有効範囲が明確になり、より安全で予測しやすいコードを書ける。現代では`const`をデフォルトに、再代入が必要な場合は`let`を使い、`var`は避けるのが推奨される。
ITニュース解説
JavaScriptを学び始めたばかりの皆さんにとって、変数宣言のvar、let、constは最初は混乱しやすい部分かもしれないが、これらの違いを理解することは、現代のJavaScriptで安全で予測可能なコードを書く上で非常に重要である。この解説では、まず歴史的に使われてきたvarの持つ「癖」を理解し、その問題点を解決するために導入されたletがどのようにコードを改善するのかを具体的に見ていく。
varの抱える問題点:スコープとホイスティングの理解
JavaScriptには「スコープ」という重要な概念がある。スコープとは、変数がコードのどの範囲で利用可能かを示すものだ。かつてはvarのみが変数宣言に用いられていたが、varにはいくつかの独特な挙動があった。
第一に、varは「関数スコープ」を持つ。これは、varで宣言された変数は、それが宣言された関数の中でのみ有効であることを意味する。もし関数内で宣言されなければ、その変数はグローバルスコープとなり、コードのどこからでもアクセスできてしまう。重要なのは、if文やforループのような波括弧{}で囲まれたブロックは、varにとって新しいスコープを作らないという点だ。
例えば、次のコードを見てみよう。
1if (true) { 2 var message = "Hello from inside the if block!"; 3 console.log(message); // "Hello from inside the if block!" 4} 5console.log(message); // "Hello from inside the if block!"
この例では、ifブロック内でmessageという変数をvarで宣言している。通常、他のプログラミング言語ではこのようなブロック内で宣言された変数はブロックの外からはアクセスできないが、varの場合はブロックの外からでもmessageにアクセスし、その値を確認できてしまう。これは、意図せず広範囲の変数に影響を与えたり、既存の変数を上書きしてしまったりする「変数の衝突」という予期せぬバグを引き起こす可能性があった。
第二に、varには「ホイスティング(巻き上げ)」という特殊な挙動がある。ホイスティングとは、JavaScriptのコードが実行される前に、var変数や関数宣言がそのスコープの先頭に物理的に移動したかのように扱われる現象のことだ。ただし、varの場合、宣言は巻き上げられるが、値の代入(初期化)は元のコードの位置で行われる。
次のコードは、この挙動を示している。
1console.log(myName); // Outputs: undefined 2var myName = "Alice"; 3console.log(myName); // Outputs: "Alice"
このコードを実行すると、myNameが宣言される前の最初のconsole.logではエラーにならず、undefinedが出力される。これは、JavaScript内部では次のように解釈されるからだ。
1var myName; // 宣言がスコープの先頭に巻き上げられ、undefinedで初期化される 2console.log(myName); // undefined 3myName = "Alice"; // 値の代入は元の位置で行われる 4console.log(myName); // "Alice"
多くのプログラミング言語では、変数を宣言する前に使用しようとすると明確なエラーが発生する。varではundefinedという結果になるため、バグを発見しにくく、初心者にとっては特に混乱の原因となりやすかった。
letの登場:より安全なブロックスコープ
これらのvarの持つ問題点を解決するために、2015年にリリースされたES6(ECMAScript 2015)でletとconstという新しい変数宣言が導入された。特にletは、変数の挙動をより論理的で予測可能なものにするための重要な進化である。
letの最大の特徴は「ブロックスコープ」であることだ。if文、forループ、whileループなど、波括弧{}で囲まれたコードの塊は全て「ブロック」として扱われる。letで宣言された変数は、それが宣言されたブロック内でのみ有効であり、ブロックの外からはアクセスできない。
先ほどのif文の例をletで書き直してみよう。
1if (true) { 2 let message = "Hello from inside the if block!"; 3 console.log(message); // "Hello from inside the if block!" 4} 5console.log(message); // ReferenceError: message is not defined
今度は、ifブロックの外でmessageにアクセスしようとすると、ReferenceErrorという明確なエラーが発生する。これは、message変数がそのブロックの外では存在しないことを意味する。この挙動は、変数の利用範囲を限定し、意図しない場所からのアクセスや上書きを防ぐため、コードの可読性と保守性を大幅に向上させる。必要な場所でだけ変数を定義し、そのスコープを明確にすることで、広範囲への影響を心配することなく安全にコードを書けるようになるのだ。
letと一時的デッドゾーン(TDZ)
letもvarと同様にホイスティングされるが、その挙動はvarよりも安全だ。let変数は、その宣言が評価されるまでは初期化されない。変数が宣言されたブロックの開始から、実際にletキーワードで変数が宣言され初期化されるまでの期間を「一時的デッドゾーン(Temporal Dead Zone、TDZ)」と呼ぶ。このTDZの期間中にlet変数にアクセスしようとすると、varのようにundefinedが返されるのではなく、ReferenceErrorが発生する。
1console.log(favoriteFood); // ReferenceError: Cannot access 'favoriteFood' before initialization 2let favoriteFood = "Pizza";
このエラーは非常に分かりやすく、変数を宣言する前に使ってしまっているという明確な問題を示してくれる。これにより、開発者はコードの記述順序により注意を払うようになり、宣言と利用の順序が強制されるため、デバッグがずっと容易になる。コードの意図が明確になり、より堅牢なプログラムを作成できるようになるのだ。
letが解決するループ内の古典的な問題
letがもたらす最も実用的な改善の一つに、ループ内での変数の挙動がある。JavaScriptの学習者にとって、varを使ったforループと非同期処理を組み合わせた際に発生する古典的な問題は大きな混乱の元だった。
次のvarを使ったコードを見てみよう。
1for (var i = 0; i < 3; i++) { 2 setTimeout(function() { 3 console.log(i); // 何が出力されるか? 4 }, 100); 5} 6// 出力: 3, 3, 3
このコードは、0、1、2と出力されることを期待するかもしれないが、実際には3が3回出力される。これは、varが関数スコープであり、forループの波括弧{}が新しいスコープを作らないためだ。ループ全体でiという変数は一つしか存在せず、setTimeoutのコールバック関数が実行される頃には、ループはすでに終了し、iの値は最終的に3になっている。全てのコールバックはこの同じiを参照するため、結果として全て3が出力される。
これをletで書き換えるとどうなるだろうか。
1for (let i = 0; i < 3; i++) { 2 setTimeout(function() { 3 console.log(i); // 何が出力されるか? 4 }, 100); 5} 6// 出力: 0, 1, 2
letを使用した場合、期待通りに0、1、2が出力される。これは、letがブロックスコープを持つため、forループの各イテレーション(繰り返し)ごとに新しいi変数が作成されるからだ。それぞれのsetTimeoutコールバックは、そのイテレーション固有のiの値を参照するため、正しい値が保持される。この機能は、非同期コードを扱う上での大きな混乱を解消し、より直感的なコード記述を可能にする。
現代における変数宣言の使い分け
現在のJavaScript開発では、varの使用は基本的に推奨されない。では、letとconstはどのように使い分けるべきだろうか。簡単なルールは次の通りである。
constをデフォルトで使用する: もし変数の値が一度代入されたら、その後再代入されることがないと確信できる場合は、constを使用すべきだ。constは再代入を許さないため、コードの意図が明確になり、誤って値を変更してしまうバグを防ぐことができるため、コードの予測可能性と安全性が高まる。letは再代入が必要な場合に使用する: 変数の値が後で変更される可能性がある場合(例えば、ループのカウンターなど)には、letを使用する。
varはもはや現代のJavaScriptプロジェクトで積極的に使用する必要はない。それはJavaScriptが進化してきた歴史の一部として捉えるべきだ。
まとめ
varとletの違いを理解することは、単に構文を覚える以上の意味がある。それはJavaScriptにおける「スコープ」の概念を深く理解し、より安全で信頼性の高いコードを書くための基礎を築くことにつながる。letとconstを適切に使いこなすことは、広範なバグを未然に防ぎ、コードの可読性を高め、メンテナンスしやすいアプリケーションを開発するための重要なスキルだ。これらの概念を習得することは、基礎的なスクリプト作成の段階を超え、プロフェッショナルなソフトウェア開発者として成長するための不可欠なステップであると言える。