JavaScript グローバルスコープのクイックガイド
JavaScript ファイルのトップレベルで変数を宣言した場合、その変数は実際にどこに存在するのでしょうか?答えは、クラシックスクリプトを記述しているのか、ES モジュールを記述しているのかによって異なります。この違いを理解していないと、デバッグが困難な微妙なバグにつながります。
このガイドでは、モダンな開発における JavaScript のグローバルスコープの仕組み、globalThis がグローバルオブジェクトを参照する信頼性の高い方法である理由、そしてトップレベルでの var、let、const の異なる動作について説明します。
重要なポイント
- グローバルスコープの動作はクラシックスクリプトと ES モジュールで異なります。クラシックスクリプトの
varのみがグローバルオブジェクトにプロパティを作成します。 letとconstはグローバルバインディングを作成しますが、グローバルオブジェクトには付加されません。- グローバルオブジェクトにアクセスする必要がある場合は、クロス環境互換性のために
globalThisを使用してください。 - 意図しない名前空間の汚染を避け、自動的なスコープ分離を実現するために ES モジュールを優先してください。
JavaScript のグローバルスコープとは?
グローバルスコープとは、JavaScript 環境における最も外側のスコープを指します。グローバルスコープ内の変数は、関数内、ブロック内、ネストされたモジュール内など、コード内のどこからでもアクセス可能です。
しかし、ほとんどのチュートリアルが見逃している重要な違いがあります:JavaScript のトップレベルスコープは、スクリプトの読み込み方法によって異なる動作をします。
クラシックスクリプト vs ES モジュール
クラシックスクリプト(type="module" なしで読み込まれる)では、var で宣言されたトップレベル変数はグローバルオブジェクトのプロパティになります:
// classic script
var config = { debug: true }
console.log(globalThis.config) // { debug: true }
ES モジュールでは、これが完全に変わります。モジュールとグローバルスコープは設計上異なる動作をします。モジュール内のトップレベルバインディングは、そのモジュールのスコープ内に留まり、グローバルオブジェクトには付加されません:
// module script (type="module")
var config = { debug: true }
console.log(globalThis.config) // undefined
この分離は意図的なものです。モジュールはカプセル化を提供し、ファイル間での偶発的な名前空間の汚染を防ぎます。より詳しい情報については、MDN の JavaScript modules ガイドを参照してください。
トップレベルでの var、let、const の違い
クラシックスクリプトにおいても、let と const は var とは異なる動作をします:
// classic script
var a = 1
let b = 2
const c = 3
console.log(globalThis.a) // 1
console.log(globalThis.b) // undefined
console.log(globalThis.c) // undefined
var のみがグローバルオブジェクトにプロパティを作成します。let と const の両方は、コード全体からアクセス可能なグローバルスコープ内にバインディングを作成しますが、グローバルオブジェクトのプロパティにはなりません。
この違いは、サードパーティのスクリプトがグローバルオブジェクト上で変数を見つけることを期待している場合や、直接デバッグしてチェックする際に重要になります。
Discover how at OpenReplay.com.
globalThis がモダンスタンダードである理由
歴史的に、異なる JavaScript 環境ではグローバルオブジェクトに異なる名前が使用されてきました:
- ブラウザは
windowを使用 - Node.js は
globalを使用 - Web Workers は
selfを使用
クロス環境コードを書くということは、どのグローバルが存在するかをチェックすることを意味していました。globalThis 標準は、普遍的な参照を提供することでこの問題を解決します:
// Works everywhere
globalThis.sharedValue = 'accessible across environments'
globalThis を使用することで、ブラウザ、Node.js、Deno、Web Worker のいずれで実行される場合でも、コードが正しく動作することが保証されます。これは正しい、プラットフォームに依存しないアプローチです。
グローバルスコープでの変数のシャドーイング
よくある誤解として:グローバルスコープに既に存在する let または const 変数を再宣言することはできません。ただし、レキシカル宣言でグローバルに導入された var バインディングをシャドーイングすることは可能です。これには、モダンエンジンで動的に(例えば eval 経由で)導入された var バインディングも含まれます:
var x = 1
{
let x = 2 // shadows the global var
console.log(x) // 2
}
console.log(x) // 1
内側の let x は、そのブロック内で外側の var x を一時的に隠す別の変数を作成します。これらは別個のバインディングであり、一方を変更しても他方には影響しません。
モジュール専用機能:トップレベル await
JavaScript 開発者が知っておくべきトップレベルスコープの実用的な違いの一つ:await はトップレベルではモジュール内でのみ動作します:
// Only valid in modules
const data = await fetch('/api/config').then(r => r.json())
クラシックスクリプトはこの構文をサポートしていません。トップレベル await が必要な場合は、スクリプトタグに type="module" を使用する必要があります。
グローバルスコープのベストプラクティス
- モジュールを優先する — 自動的なスコープ分離と明示的な import/export を提供します
globalThisを使用する — グローバルオブジェクトが本当に必要な場合、これはどこでも動作します- トップレベルでの
varを避ける — 意図しないグローバルオブジェクトの汚染を防ぐためにletまたはconstを使用します - グローバルについて明示的にする — 何かがグローバルである必要がある場合は、暗黙的な動作に依存するのではなく、意図的に
globalThisに付加します
まとめ
JavaScript のグローバルスコープは「どこからでもアクセス可能な変数」というほど単純ではありません。クラシックスクリプトと ES モジュールはトップレベルバインディングを異なる方法で処理し、クラシックスクリプトの var のみがグローバルオブジェクトのプロパティを作成します。クロス環境互換性のために globalThis を使用し、組み込みのカプセル化のためにモジュールを優先してください。これらの違いを理解することで、異なる JavaScript 環境で動作する予測可能なコードを書くことができます。
よくある質問
はい、ただし両方のスクリプトがクラシックスクリプトであり、同じグローバルスコープを共有している場合に限ります。変数は名前によってスクリプト間でアクセス可能ですが、グローバルオブジェクトのプロパティではありません。いずれかのスクリプトが ES モジュールである場合、変数は明示的にエクスポートされない限り、そのモジュールのプライベートなままです。
制御できないスクリプト(サードパーティの分析ツールやグローバルを期待するレガシーコードなど)とデータを共有する必要がある場合に、グローバルオブジェクトを使用してください。また、普遍的に利用可能である必要があるポリフィルにも使用します。制御できるコードについては、より良いカプセル化と明示的な依存関係追跡のためにモジュールエクスポートを優先してください。
globalThis は、すべてのモダンな JavaScript 環境でサポートされています。Internet Explorer などのレガシープラットフォームをターゲットにする必要がある場合は、ポリフィルを使用するか、バンドラー/トランスパイラーに提供させてください。
これは歴史的な設計上の決定です。var は JavaScript の最初から存在し、相互運用性のためにグローバルオブジェクトのプロパティを作成するように設計されました。let と const は ES6 で導入され、グローバルアクセス可能性を維持しながらも、暗黙的なグローバル汚染の落とし穴を避けるために、より厳格なスコープルールが適用されています。