JavaScriptのvar、let、const:この「地獄のトリオ」を解説(TypeScriptのヒント付き)
JavaScriptの変数で頭を抱えている開発者の皆さん!チームミーティングでvar、let、constの使い分けに困ったことはありませんか?心配いりません。一緒に楽しく解決していきましょう。各キーワードを探求し、スコープ(グローバル、関数、ブロック)を比較し、ホイスティング(JavaScriptのちょっと不思議な機能)について議論し、実践的な例を提供し、最後にTypeScriptについても触れていきます。準備はいいですか?それでは始めましょう!
varキーワード:予測不可能な「大先輩」
まずはvarから始めましょう。JavaScriptの変数の中でも「大先輩」です。JavaScriptがドラマだったら、varは古風で予測不可能なキャラクターでしょう。ES5以前は、変数を宣言するにはvarしかありませんでした。今でも使うことはできますが、注意が必要です:いくつかの驚くべき特性があります。
varの特徴
- varのスコープ:varで宣言された変数は、それを含む関数内に制限されます(関数外で宣言された場合はグローバル)。ブロックスコープ(if、forなど)を無視します。つまり、ブロック内で宣言されたvar変数は、同じ関数内であればブロック外からもアクセスできます。
- 再宣言が可能:varでは、同じスコープ内で同じ変数を複数回宣言してもエラーになりません。例えば:
var x = 1; var x = 2;はエラーを引き起こしません——JavaScriptは単に最初の値を2番目の値で上書きします。古いコードでは便利ですが、新しいコードでは混乱の原因となるので避けるべきです。
varの広すぎるスコープを小さな例で説明しましょう:
javascript
はい、このfruitはif文の中で宣言されているにもかかわらず、ブロック外のconsole.logでも問題なくアクセスできます。varはブロックの境界を気にしません:グローバルか、それを含む関数のどちらかです。
最後に、ホイスティングについて話しましょう。日本語では「巻き上げ」と呼ばれることもありますが、正直なところ、私たちは主にhoistingという用語を使います。これはvarがスコープの先頭で静かに宣言される傾向のことです。JavaScriptは実行時に、すべてのvar宣言が関数の先頭に「巻き上げられた」かのように振る舞います。そのため、宣言の前でも変数を使用できます...ただし、少し奇妙な値が得られます。この問題については、後ほどホイスティングのセクションで詳しく説明します(ネタバレ:varはundefinedで驚かせるのが大好きです)。
全体として、varは私たちに良いサービスを提供してきましたが、いくつかの狡猾な特性があります:広すぎるスコープ、静かな再宣言、予測不可能なホイスティング...もっと良い方法があります。幸いなことに、ES6は新しいキーワードを導入して、すべてを整理しました。
letキーワード:行儀の良い新しい変数
2015年、JavaScriptはlet(とconst)を迎え入れました。そして、変数宣言の小さなアップグレードを得ました。letはクールで規律正しい若者のようなものです:varのほとんどの癖を修正しながら、柔軟性を保っています。
letの特徴
- letのスコープ:letで宣言された変数は、定義されたブロック内に制限されます。ブロックとは { ... } で囲まれたコードのことです——関数、if文、forループなど。ブロック内でlet変数を宣言すると、ブロック外では存在しません。ついに秩序が!変数がif文やループから許可なく逃げ出すことはなくなりました。
- 再宣言は不可:varとは異なり、同じスコープ内で同じlet変数を2回宣言することはできません。同じブロック内で
let x = 1; let x = 2;と書こうとすると、素敵なSyntaxError: Identifier 'x' has already been declaredエラーが発生します。これで多くの混乱を避けられます。
varをletに置き換えて、スコープの違いをはっきりと見てみましょう:
javascript
letを使うと、if文の波括弧を出た瞬間に終わりです。vegetable変数はもはや定義されていません。アクセスしようとすると:ReferenceErrorが発生します。これがletが良好なスコープの振る舞いを強制する方法です。
では、ホイスティングはどうでしょうか?let(とconst)変数もホイスティングされますが、方法が異なります。宣言前には使用できません。技術的には、JSエンジンはブロック内に変数が存在することを知っています(メモリを確保します)が、宣言文が実行されるまでは、アクセスしようとするとエラーが発生します。これらの変数は一時的なデッドゾーンにあると言います(はい、ホラー映画のように聞こえますが、これは「初期化前はアクセス不可」の洒落た言い方です)。この振る舞いについては後で詳しく説明しますが、letは変数を早すぎる段階で使用させないことを覚えておいてください。
実際には、letは時間とともに変化する変数のデフォルト選択肢となりました。明確さ(ブロックスコープ)と安全性(予期しない再宣言なし、宣言前のアクセスなし)をもたらします。簡単に言えば、letは一時的または変更可能な変数を宣言するための新しい友達です。
constキーワード:安全な賭け(ただし凍結ではない)
次はconstです!letと同時に導入され、このキーワードは定数を作成します...と言いたいところですが、そうではありません。参照が変更されない変数を作成します。constはletと同じブロックスコープ(もうvarがうろつくことはありません)と同じホイスティングルール(宣言までの一時的なデッドゾーン)を持っています。大きな違いは、一度初期化されると、変数は再代入できなくなることです。
constの特徴
- 初期化が必須:constでは、宣言時に値を与える必要があります。選択の余地はありません。
const x;だけでは動作しません(直接SyntaxError)。const x = 42;のように書く必要があります。 - 再代入不可:一度定数が定義されると、後で新しい値を与える方法はありません。試みると、実行時にエラー(TypeErrorで"assignment to constant variable")が発生します。値は固定されます...まあ、参照が固定されます。
- ブロックスコープで再宣言不可:letと同様に、const変数は宣言されたブロック内に存在し、同じスコープ内で同じ名前を2回宣言することはできません。きれいです。
変更可能な変数との違いを明確に示す小さな例を見てみましょう:
javascript
PIを定数として定義し、変更しようとしなければ問題なく使用できます。PI = 3.15を試みると、JavaScriptは即座に私たちを止めます:定数は変更できません。
注意:定数は100%不変という意味ではありません。値がオブジェクトや配列の場合、オブジェクト/配列の内部は変更できます。変更できないのは変数自体(メモリ内の参照)です。例えば:
javascript
ここでは、constを使用しているにもかかわらず、userオブジェクトのnameを変更できました。しかし、userを完全に別のオブジェクトに再代入することは禁止されています。したがって、const = 定数、変更不可、ただし変数自体のレベルでのみです。変数の定数性と値の不変性を混同しないでください。
javascript
配列の場合も同じロジックです:内容を変更できます(要素の追加、削除、変更)が、fruits変数を新しい配列に再代入することはできません。配列が箱のようなものだと考えると:中身は変更できますが、箱自体を置き換えることはできません。
実際には、constは変更されるべきでない値に最適です:例えば設定、参照など。広く使用され、コードをより堅牢にします(この変数が動かないことがわかっています)。実際、ベストプラクティスでは、デフォルトでconstを使用し、値が変更される必要があることがわかっている場合のみletに切り替えることを推奨しています。varについては...まあ、特別な理由がない限り、忘れましょう 😉。
変数のスコープ:グローバル、関数、それともブロック?
スコープについてもっと話しましょう。スコープは変数がコード内の「どこ」でアクセス可能かを決定します。JavaScriptには主に3つのスコープレベルがあります:
スコープの種類
- グローバルスコープ:変数がどの関数の外でも宣言された場合(メインスクリプトやコンソールで)、それはグローバルです。その後、どこでもアクセスできます(どの関数やブロック内でも)。⚠️ 注意、モジュール外では、グローバルvarはグローバルオブジェクトのプロパティになります(ブラウザではwindowなど)、一方letとconstは「クリーン」なグローバル変数を作成します(windowに付属しません)。ES6モジュールでは、トップレベルの変数はグローバルオブジェクトにまったく漏れません。
- 関数スコープ:これは関数内で作成されるスコープです。関数内で宣言されたvar変数は、その関数内でのみ見えます(およびそのサブ関数がある場合)。外からは見えません。関数内で宣言されたletとconstも同様です:それらはその関数のローカルスコープ内に留まります。
- ブロックスコープ:これは { ... } で区切られたブロック内に制限されるスコープです(例えばif文、forループ、whileループ内、または単純な独立したブロック)。letとconstはブロックスコープを持ち、定義されたブロック内でのみアクセス可能です(およびそのサブブロック)。一方、varはブロックスコープを持ちません:その参照ブロックは包含する関数(関数がない場合はグローバル)です。
まとめると:varは関数(またはグローバル)に制限され、letとconstは現在のブロックに制限されます。この違いは多くの古典的な問題を解決します。例えば:
javascript
ここでは、varを使用したループはiを外に残し、最終値3を持っています。letを使用したループは、一方で、終了時にjをクリーンアップします:その後アクセスできません。実際には、これは異なるブロックで同じ名前のlet変数を使用でき、相互に干渉しないことを意味します。一方、単一のvarはこれらのブロック間で共有されます。
ホイスティング:JavaScriptがかくれんぼをするとき
ホイスティングについて話しましょう。この宣言の巻き上げメカニズムは、しばしば混乱の源(そして会議でのジョーク)になります。ホイスティングは、JavaScriptエンジンが実行中に変数と関数の宣言をそれらのスコープの先頭に「移動」する動作です。実際には、コード内で何かが本当に移動するわけではありません。これはJavaScriptが実行前に変数のメモリを事前に割り当てるだけです。
ホイスティングの振る舞い
- varの場合:宣言はホイスティングされ、デフォルトでundefinedに初期化されます。したがって、宣言行の前でもvar変数を使用できます。それはすでに存在しています(実際の代入が後で到達するまでundefinedの値を持ちます)。これは言語の合法な動作ですが、注意しないとかなり厄介なバグを引き起こす可能性があります。
- letとconstの場合:宣言もホイスティングされます(エンジンはこのブロックに変数があることを知っています)が、デフォルトの初期化は行われません。変数は宣言行が実行されるまで一時的なデッドゾーンに置かれます。実際には、これは実際の宣言前に変数を読み取ろうとしたり使用しようとしたりすると、JavaScriptがエラー(ReferenceError)をスローすることを意味します。言い換えれば、変数は理論的には存在しますが、その宣言/初期化が実行されるまではアクセスできません。
実際にホイスティングをテストしてみましょう:
javascript
最初のconsole.logの時点で、myVar変数はホイスティングによってメモリ内に作成されていますが、そのデフォルト値はundefinedです(まだ代入していないため)。エラーはなく、undefinedが表示されるだけです。その後「こんにちは」を代入し、2回目は期待通りの値が得られます。
次にletを見てみましょう:
javascript
ここでは、最初のconsole.logがReferenceErrorを引き起こします。なぜ?myLetはホイスティングされますが初期化されないためです。それは一時的なデッドゾーンにあり、アクセスできません。JavaScriptはまだ本当に存在しない値を私たちに与えることを拒否します。let myLet = "こんにちは";を実行すると、変数は一時的なデッドゾーンを出て「こんにちは」を取得します。2番目のconsole.logは完璧に動作します。
はっきりと見えるように:varは忍者のように宣言を巻き上げて静かにundefined値を自分に与えますが、let/constは安全策を講じ、初期化されるまで使用を防ぎます。教訓:ホイスティングに頼ってコーディングするのは避けましょう。それは脳の結び目(と予期しないundefined)のレシピです。スコープの先頭で変数を宣言し、コードをクリーンに保つのが最善です。忘れた場合、let/constの振る舞いがエラーで思い出させてくれます。varはundefinedで苦労させていたでしょう。
あ、そうそう、従来の関数宣言(function myFunction() \{ ... \})も完全にホイスティングされることに注意してください。コードの後方で宣言された関数を呼び出すことができ、JavaScriptはすでにそれを知っています。ただし、アロー関数や変数に代入された関数式は、使用されるキーワードに応じてvar/letのホイスティングルールに従います(これは別の日の話題 😉)。
TypeScript小話:より明確に
終わる前に、TypeScriptについて簡単に話しましょう。TypeScript(JavaScriptに静的型付けを追加するスーパーセット)を使い始めると、私たちの3人の友達var、let、constに出会います。良いニュース:それらのスコープとホイスティングの振る舞いは純粋なJavaScriptとまったく同じです。TypeScriptは標準JavaScriptにコンパイルされるためです。しかし、TypeScriptは型付けの面で独自の特色を持ち、特にletとconstについては見る価値があります。
TypeScriptと変数
- 型推論:TypeScriptは初期値に基づいて変数の型を推測します。
let fruit = "りんご";と書くと、TypeScriptはfruitがstring型だと推論します。let age = 25;と書くと、ageが数値だと理解します。letの場合、かなり広範です:変数は変更可能なので、型は基本型(string、numberなど)です。 - 定数とリテラル型:興味深いことに、constを使用すると、TypeScriptはリテラル型を推論します。言い換えれば、値が定数なので、TSは「この変数の型はこの正確な値」と言うことができます。例えば:
typescript
color変数は"赤"型であり、単なるstringではありません。利点?後でコードが正確に"赤"または"青"の値を期待する場合(例えば、これらの色のいずれかしか受け付けないパラメータ)、const color = "赤"があれば、心配なく渡せます。let color = "赤"(広範なstring型)では、TypeScriptは文句を言うでしょう。"赤"はすべての文字列の中の1つの可能性に過ぎないからです。簡単に言えば、TypeScriptのconstは有用な時に超精密な型定義を可能にします。これは定数の概念をさらに強化するボーナスのようなものです。
- 同じ規律:TypeScriptはES6と同様にletとconstの使用を奨励します。実際、ほとんどのTypeScriptプロジェクトはvarを完全に禁止しています(linterルールさえあります)。宣言前に変数を使用しようとすると、TypeScriptはコンパイル時に叫びます——JavaScriptのランタイムがそうするずっと前に。例えば、TypeScriptコードで
console.log(myVar); var myVar = 3;と書くと、スコープや順序の問題があることを指摘します。letを早すぎる段階で使用する場合も同様で、TSはホイスティングルールを知っており、可能な限りエラーから保護します。
まとめると、TypeScriptはvar/let/constの基本ルールを変更しませんが、型システムの安全ネットでそれらを強化します。明確さを得て、エラーをより早くキャッチします。特にTypeScriptのconstは二重の勝利であることを覚えておいてください:再代入不可能な変数と正確なリテラル型——これ以上何を求めることができますか?
結論:間違えない選択方法
すべてをカバーしました。では、var vs let vs constの戦いの勝者は誰でしょうか?驚くことではありません:2025年では、let & constの組み合わせが明確で保守可能なコードを書くために圧倒的に勝っています。ここに実践的なアドバイスがあります:
ベストプラクティス
- 可能な限りconstを使用:値が変更される必要のない変数には、constが最良の友です。再代入をブロックし、すべての人(6ヶ月後のあなた自身を含む)にこの値が動かないことを明確に示します。設定、固定参照などに最適です。
- 値を変更する必要がある場合はletを使用:カウンターをインクリメントしたり、結果を蓄積したり、変更可能な中間値を保存したりする必要がありますか?letはそのためにあります。良好な振る舞い(ブロックスコープ、予期しないホイスティングの罠なし)を提供し、値を変更する柔軟性を与えてくれます。
- 特別な場合を除いてvarは避ける:正直なところ、varは日常の語彙からほぼ消えることができます。後方互換性といくつかの特定のシナリオのために生き残っていますが、現代のコードではあまり流行していません。古いプロジェクトを扱っている場合や、意図的にグローバルスコープを操作する必要がある場合(通常は避けるべきこと)は、まあ、varが顔を出すかもしれません。それ以外の場合は、JavaScriptの遺物博物館に置いておきましょう 😄。
- 疑問がある場合は説明する:もしある日varを使用して、会議でなぜかと聞かれたら、良い理由を持って説明してください(例えば、「古いスクリプトがアクセスできるように、この変数をグローバルに宣言する必要があります」)。しかし、99%の時間、このような体操は必要ありません:letとconstがすべてのニーズをカバーし、瞬きもしません。
これで、JavaScriptでいつvar、let、またはconstを使用するか、そしてTypeScriptでの動作についても少し理解できました。もう私たちの3人の友達について混乱することも、コードレビューで理由もなくvarを出して笑われることもありません。落ち着いてコーディングに行きましょう。そして忘れないでください:可能な限りconst、必要な場合のみlet、そしてvarは...できるだけ少なく!ハッピーコーディング!