JavaScript の深層理解
この本のサポートに ご協力ください: 購入する寄付する
(広告、ブロックしないでください。)

5 グローバル変数の詳細の確認



この章では、JavaScript のグローバル変数がどのように機能するかを詳しく検討します。興味深い現象がいくつか関連しています。スクリプトのスコープ、いわゆる「グローバルオブジェクト」などです。

5.1 スコープ

変数のレキシカルスコープ(短縮してスコープ)は、変数がアクセスできるプログラムの領域です。JavaScript のスコープは静的(ランタイム時には変更されません)で入れ子にできます – たとえば

function func() { // (A)
  const aVariable = 1;
  if (true) { // (B)
    const anotherVariable = 2;
  }
}

if ステートメント(行 B) によって導入されたスコープは、関数 func()(行 A)のスコープの中に入れ子になっています。

スコープ S の最も内側の周囲のスコープは、S の外部スコープと呼ばれます。この例では、funcif の外部スコープになります。

5.2 レキシカル環境

JavaScript の言語仕様では、スコープは レキシカル環境を使用して「実装」されます。レキシカル環境は、次の 2 つのコンポーネントで構成されます。

したがって、入れ子のスコープのツリーは、外部環境レファレンスによってリンクされた環境のツリーで表されます。

5.3 グローバルオブジェクト

グローバルオブジェクトは、そのプロパティがグローバル変数になるオブジェクトです。(具体的に環境のツリーにどのように収まるかについては、後で説明します。)次のグローバル変数を使用してアクセスできます。

5.4 ブラウザで、globalThis は グローバルオブジェクトを直接指しません

ブラウザで、globalThis は グローバルを直接指さず、間接的な参照になります。たとえば、ウェブページの iframe を考えてみます。

ファイル parent.html

<iframe src="iframe.html?first"></iframe>
<script>
  const iframe = document.querySelector('iframe');
  const icw = iframe.contentWindow; // `globalThis` of iframe

  iframe.onload = () => {
    // Access properties of global object of iframe
    const firstGlobalThis = icw.globalThis;
    const firstArray = icw.Array;
    console.log(icw.iframeName); // 'first'

    iframe.onload = () => {
      const secondGlobalThis = icw.globalThis;
      const secondArray = icw.Array;

      // The global object is different
      console.log(icw.iframeName); // 'second'
      console.log(secondArray === firstArray); // false

      // But globalThis is still the same
      console.log(firstGlobalThis === secondGlobalThis); // true
    };
    iframe.src = 'iframe.html?second';
  };
</script>

ファイル iframe.html

<script>
  globalThis.iframeName = location.search.slice(1);
</script>

ブラウザはこのシナリオで globalThis が変化しないようにどのように保証しますか?ブラウザは、内部で 2 つのオブジェクトを区別します。

ブラウザでは、globalThisWindowProxy を参照し、他の場所ではグローバルオブジェクトを直接参照します。

5.5 グローバル環境

グローバールスコープは「最上位」スコープであり、外部スコープがありません。その環境はグローバル環境です。すべての環境は、外部環境参照でリンクされた環境のチェーンを介してグローバル環境に接続されています。グローバル環境の外部環境参照は null です。

グローバル環境レコードは、その変数を管理するために 2 つの環境レコードを使用します。

これらの 2 つの記録のどちらがいつ使用されるかは、後で説明します。

5.5.1 スクリプトルスコープとモジュールスコープ

JavaScript では、スクリプトの最上位レベルでのみグローバルスコープになります。対照的に、各モジュールにはそれ自身のスコープがあり、それはスクリプトルスコープのサブスコープです。

グローバル環境にどのように変数のバインディングが追加されるかという比較的複雑なルールを無視すると、グローバルスコープとモジュールスコープはネストされたコードブロックのように機能します。

{ // Global scope (scope of *all* scripts)

  // (Global variables)

  { // Scope of module 1
    ···
  }
  { // Scope of module 2
    ···
  }
  // (More module scopes)
}

5.5.2 変数の作成:宣言的レコード対オブジェクトレコード

真にグローバルな変数を作成するには、グローバルスコープにいる必要があります。これはスクリプトの最上位でのみ行われます。

<script>
  const one = 1;
  var two = 2;
</script>
<script>
  // All scripts share the same top-level scope:
  console.log(one); // 1
  console.log(two); // 2
  
  // Not all declarations create properties of the global object:
  console.log(globalThis.one); // undefined
  console.log(globalThis.two); // 2
</script>

5.5.3 変数の取得または設定

変数の取得または設定を行い、両方の環境レコードがその変数のバインディングを持っている場合、宣言的レコードが優先されます。

<script>
  let myGlobalVariable = 1; // declarative environment record
  globalThis.myGlobalVariable = 2; // object environment record

  console.log(myGlobalVariable); // 1 (declarative record wins)
  console.log(globalThis.myGlobalVariable); // 2
</script>

5.5.4 グローバル ECMAScript 変数とグローバルホスト変数

var と関数宣言を介して作成された変数に加えて、グローバルオブジェクトには次のプロパティが含まれています。

const または let を使用すると、グローバル変数の宣言が ECMAScript とホストプラットフォームの組み込みグローバル変数に影響を与えない(または影響されない)ことが保証されます。

たとえば、ブラウザには グローバル変数 .location があります。

// Changes the location of the current document:
var location = 'https://example.com';

// Shadows window.location, doesn’t change it:
let location = 'https://example.com';

変数が既に存在する場合(この場合の location など)、初期化子が付いた var 宣言は代入のように動作します。そのため、この例の問題が発生します。

これはグローバルスコープでのみ問題になることに注意してください。モジュールでは、グローバルスコープにはなりません( `eval()` や類似のものを使用する場合を除く)。

図 10 は、このセクションで学習したすべての内容をまとめています。

図 10: グローバルスコープの環境は、2 つの環境レコードに基づいて結合を管理する *グローバル環境レコード* を通じてその結合を管理します。1 つ目は、結合がグローバルオブジェクトに格納される *オブジェクト環境レコード* で、もう 1 つは結合に内部ストレージを使用する *宣言環境レコード* です。したがって、グローバル変数は、グローバルオブジェクトにプロパティを追加するか、さまざまな宣言によって作成できます。グローバルオブジェクトは、ECMAScript の組み込みグローバル変数とホストプラットフォームで初期化されます。各 ECMAScript モジュールには独自の外側環境がグローバル環境である環境があります。

5.6 結論: JavaScript には通常のグローバル変数とグローバルオブジェクトの両方が必要なのはなぜですか?

グローバルオブジェクトは通常、間違いとみなされます。そのため、 `const`、`let`、およびクラスなどの新しい構成要素は、(スクリプスコープの場合に)通常のグローバル変数を作成します。

幸いにも、最新の JavaScript で記述されたコードの大部分は、ECMAScript モジュールと CommonJS モジュール 内にあります。各モジュールには独自のスコープがあり、それが、グローバル変数を決定するルールがモジュールベースのコードにほとんど関係しない理由です。

5.7 この章のさらなる読み物とソース

ECMAScript 仕様における環境とグローバルオブジェクト

globalThis:

ブラウザのグローバルオブジェクト