こんにちは!フロントエンドからNode.jsの深層まで、JavaScriptのすべてを愛するチーフアーキテクトの私です。
今回は、JavaScriptを学び始めた誰もが一度は遭遇し、そして「なんだこれ?」と首を傾げる謎――「なぜ `var` で宣言した変数は `window` オブジェクトのプロパティになるのに、`let` や `const` だとならないのか?」 というテーマについて、ECMAScript仕様の裏側から徹底的に解剖していきましょう。
ここをクリアすれば、JavaScriptの変数とスコープの仕組み、そしてグローバル環境の正体が完全に腑に落ちます。さあ、一緒に扉を開けていきましょう!
—
1. そもそも「グローバルオブジェクト」って何者?
JavaScriptを実行する環境(ブラウザならタブ、Node.jsならプロセス)には、コードのどこからでもアクセスできる「大本(おおもと)」の空間が存在します。ブラウザの世界では、それが `window` という巨大なオブジェクトです。
最近のモダンな仕様では、環境(ブラウザ、Node.js、Web Workerなど)を問わずにこのグローバルオブジェクトへアクセスできるように `globalThis` という標準プロパティも用意されています。
まずは、日常的なコードでこのグローバル空間がどうなっているか見てみましょう。
// グローバルスコープ(一番外側)で変数を宣言してみます
var globalVar = “私はvarです”;
let globalLet = “私はletです”;
// ブラウザのコンソールなどで確認してみる
console.log(window.globalVar); // “私はvarです” (おっ、見えるぞ!)
console.log(window.globalLet); // undefined あれっ、見えない!?
不思議ですよね。同じようにグローバル空間のトップレベルで宣言したのに、`var` は `window` のプロパティになり、`let` は無視されてしまいます。この「奇妙な関係」の裏側には、JavaScriptの歴史と、言語仕様(ECMAScript)の進化が深く関わっています。
—
2. なぜ `var` は `window` のプロパティになるのか?
JavaScriptの初期の歴史を紐解くと、当時の設計思想では「グローバルスコープで宣言された変数は、すべてグローバルオブジェクトのプロパティであるべきだ」というルールになっていました。
JavaScriptエンジン(V8など)の内部仕様書(ECMAScript Specification)では、スクリプトのトップレベルで `var` を使って変数を宣言すると、それは単なるローカルな変数ではなく、グローバルオブジェクト(Global Object)のプロパティ(属性)として直接バインド(結合)されるというアルゴリズムになっています。
イメージとしては、こういう状態です:
[グローバルオブジェクト (window)]
├── globalVar: “私はvarです” <-- varで宣言した変数がそのままプロパティに!
└── ほかの標準メソッドやプロパティ...
つまり、`var x = 10;` と書くことは、歴史的背景から `window.x = 10;` と書くのとほぼ同じ意味を持ってしまっていたのです。これが、意図せずグローバル変数を書き換えてしまったり、思わぬバグ(名前の衝突)を生む元凶となっていました。
---
3. `let` と `const` がたどった、モダンで洗練された運命
「グローバル空間が変数で汚染されるのは危険だ!」
そう考えた現代のJavaScript(ES2015 / ES6以降)では、新しく `let` と `const` が導入されました。
これらは、先ほどの `var` のような「グローバルオブジェクトのプロパティを汚染する仕様」をきっぱりと捨て去りました。その代わりに登場したのが、「グローバルLexical Environment(レキシカル環境)」 という、より安全で厳格な管理空間です。
イメージ図で比較してみましょう。
【varの世界】
グローバルスコープ = グローバルオブジェクト (window)
└─ var a ==> window.a として露出する
【let / constの世界】
グローバルスコープ
├─ グローバルオブジェクト (window) (※プロパティとしては追加されない)
└─ 隠されたグローバルLexical Environment
└─ let b / const c ==> ここに安全に閉じ込められる
`let` や `const` で宣言された変数は、ブラウザの `window` や `globalThis` のプロパティとしては表に出てきません。そのため、他のライブラリやスクリプトが持っている既存のグローバルプロパティ(例えば `window.name` や `window.location` など)をうっかり上書きしてしまう事故を防げるようになっています。
—
4. 実際のコードで挙動の違いを体感しよう
では、開発現場やデバッグで役立つ具体的なコードで、この違いをさらに深く確認してみましょう。
// — 1. var の挙動 —
var oldSchool = “レトロな変数”;
console.log(window.oldSchool); // “レトロな変数” 出力されます
console.log(this.oldSchool); // “レトロな変数” トップレベルの this も window を指します
// — 2. let / const の挙動 —
let modernLet = “モダンな変数”;
const modernConst = “定数です”;
console.log(window.modernLet); // undefined (プロパティになっていない!)
console.log(window.modernConst); // undefined (こちらもプロパティではない!)
// — 3. 思わぬ落とし穴:プロパティの存在チェック —
// windowオブジェクト自体に同名のプロパティがある場合
var name = “私の名前”; // 実は window.name はブラウザの標準プロパティ(初期値は空文字 “”)
console.log(window.name); // “私の名前” に書き換わってしまう!
let status = “active”;
// let であれば window.status という既存のブラウザプロパティ(ステータスバー表示用など)と競合しない!
このように、`var` を使っていると、ブラウザが元々持っているビルトインのプロパティ(`name`, `status`, `location` など)と名前がバッティングしてしまい、予期せぬ挙動を引き起こすリスクがあったのです。
—
5. 陥りがちな文法エラーと「TDZ(一時的死領域)」の秘密
ここで、初学者がよくハマるポイントについても触れておきましょう。
`let` や `const` は、プロパティにならないだけでなく、「巻き上げ(Hoisting)」の挙動も `var` とは異なります。
// var の場合:巻き上げられて undefined になる(エラーにはならない)
console.log(hoge); // undefined
var hoge = 100;
// let / const の場合:エラーになる!
console.log(fuga); // ReferenceError: Cannot access ‘fuga’ before initialization
let fuga = 200;
`let` や `const` も実は内部的には巻き上げが行われていますが、宣言のコード行に到達するまでの間は TDZ(Temporal Dead Zone:一時的死領域) という特殊な保護空間に置かれます。この空間にいる変数にアクセスしようとすると、JavaScriptエンジンが即座に `ReferenceError`(参照エラー)を投げ、バグの早期発見を助けてくれるのです。
—
まとめ:ここをクリアすれば、JavaScriptの基本はバッチリマスターできますよ!
いかがでしたでしょうか? 今回のポイントを簡潔にまとめます。
- `var` は歴史的経緯から、グローバルスコープで宣言すると `window`(グローバルオブジェクト)のプロパティになってしまう(=変数汚染のリスクがある)。
- `let` / `const` は、モダンなLexical Environmentで管理されるため、`window` のプロパティを汚染せず、より安全で予測可能なスコープを実現している。
- 予期せぬバグやプロパティの競合を防ぐため、現代のJavaScript開発では `var` の使用を避け、基本は `const`、再代入が必要な場合のみ `let` を使う のがベストプラクティス。
この「グローバルオブジェクトと変数宣言の裏側」を理解しておくと、フレームワーク(ReactやVueなど)のビルド環境や、Node.jsのモジュールシステム(モジュール内ではトップレベルの `var` でさえ `window` に追加されないことなど!)を学ぶときにも、知識がスルスルと繋がっていきます。
ぜひ、日々のコーディングで意識してみてください。あなたのエンジニアリングライフがより一層素晴らしいものになるよう、いつも応援しています!