Back

JavaScript グローバルスコープのクイックガイド

JavaScript グローバルスコープのクイックガイド

JavaScript ファイルのトップレベルで変数を宣言した場合、その変数は実際にどこに存在するのでしょうか?答えは、クラシックスクリプトを記述しているのか、ES モジュールを記述しているのかによって異なります。この違いを理解していないと、デバッグが困難な微妙なバグにつながります。

このガイドでは、モダンな開発における JavaScript のグローバルスコープの仕組み、globalThis がグローバルオブジェクトを参照する信頼性の高い方法である理由、そしてトップレベルでの varletconst の異なる動作について説明します。

重要なポイント

  • グローバルスコープの動作はクラシックスクリプトと ES モジュールで異なります。クラシックスクリプトの var のみがグローバルオブジェクトにプロパティを作成します。
  • letconst はグローバルバインディングを作成しますが、グローバルオブジェクトには付加されません。
  • グローバルオブジェクトにアクセスする必要がある場合は、クロス環境互換性のために 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 の違い

クラシックスクリプトにおいても、letconstvar とは異なる動作をします:

// 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 のみがグローバルオブジェクトにプロパティを作成します。letconst の両方は、コード全体からアクセス可能なグローバルスコープ内にバインディングを作成しますが、グローバルオブジェクトのプロパティにはなりません。

この違いは、サードパーティのスクリプトがグローバルオブジェクト上で変数を見つけることを期待している場合や、直接デバッグしてチェックする際に重要になります。

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" を使用する必要があります。

グローバルスコープのベストプラクティス

  1. モジュールを優先する — 自動的なスコープ分離と明示的な import/export を提供します
  2. globalThis を使用する — グローバルオブジェクトが本当に必要な場合、これはどこでも動作します
  3. トップレベルでの var を避ける — 意図しないグローバルオブジェクトの汚染を防ぐために let または const を使用します
  4. グローバルについて明示的にする — 何かがグローバルである必要がある場合は、暗黙的な動作に依存するのではなく、意図的に globalThis に付加します

まとめ

JavaScript のグローバルスコープは「どこからでもアクセス可能な変数」というほど単純ではありません。クラシックスクリプトと ES モジュールはトップレベルバインディングを異なる方法で処理し、クラシックスクリプトの var のみがグローバルオブジェクトのプロパティを作成します。クロス環境互換性のために globalThis を使用し、組み込みのカプセル化のためにモジュールを優先してください。これらの違いを理解することで、異なる JavaScript 環境で動作する予測可能なコードを書くことができます。

よくある質問

はい、ただし両方のスクリプトがクラシックスクリプトであり、同じグローバルスコープを共有している場合に限ります。変数は名前によってスクリプト間でアクセス可能ですが、グローバルオブジェクトのプロパティではありません。いずれかのスクリプトが ES モジュールである場合、変数は明示的にエクスポートされない限り、そのモジュールのプライベートなままです。

制御できないスクリプト(サードパーティの分析ツールやグローバルを期待するレガシーコードなど)とデータを共有する必要がある場合に、グローバルオブジェクトを使用してください。また、普遍的に利用可能である必要があるポリフィルにも使用します。制御できるコードについては、より良いカプセル化と明示的な依存関係追跡のためにモジュールエクスポートを優先してください。

globalThis は、すべてのモダンな JavaScript 環境でサポートされています。Internet Explorer などのレガシープラットフォームをターゲットにする必要がある場合は、ポリフィルを使用するか、バンドラー/トランスパイラーに提供させてください。

これは歴史的な設計上の決定です。var は JavaScript の最初から存在し、相互運用性のためにグローバルオブジェクトのプロパティを作成するように設計されました。let と const は ES6 で導入され、グローバルアクセス可能性を維持しながらも、暗黙的なグローバル汚染の落とし穴を避けるために、より厳格なスコープルールが適用されています。