Deep JavaScript
Bitte unterstützen Sie dieses Buch: kaufen Sie es oder spenden Sie
(Werbung, bitte nicht blockieren.)

5 Ein detaillierter Blick auf globale Variablen



In diesem Kapitel werfen wir einen detaillierten Blick darauf, wie globale Variablen in JavaScript funktionieren. Mehrere interessante Phänomene spielen eine Rolle: der Scope von Skripten, das sogenannte globale Objekt und mehr.

5.1 Scopes

Der lexikalische Scope (kurz: Scope) einer Variablen ist der Bereich eines Programms, in dem sie zugänglich ist. JavaScripts Scopes sind statisch (sie ändern sich nicht zur Laufzeit) und sie können verschachtelt sein – zum Beispiel

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

Der durch die if-Anweisung (Zeile B) eingeführte Scope ist in den Scope der Funktion func() (Zeile A) verschachtelt.

Der innerste umgebende Scope eines Scopes S wird als äußerer Scope von S bezeichnet. Im Beispiel ist func der äußere Scope von if.

5.2 Lexikalische Umgebungen

In der JavaScript-Sprachspezifikation werden Scopes über lexikalische Umgebungen „implementiert“. Sie bestehen aus zwei Komponenten

Der Baum der verschachtelten Scopes wird daher durch einen Baum von Umgebungen dargestellt, die durch Verweise auf äußere Umgebungen verbunden sind.

5.3 Das globale Objekt

Das globale Objekt ist ein Objekt, dessen Eigenschaften zu globalen Variablen werden. (Wir werden bald untersuchen, wie genau es in den Baum der Umgebungen passt.) Es kann über die folgenden globalen Variablen zugegriffen werden

5.4 In Browsern zeigt globalThis nicht direkt auf das globale Objekt

In Browsern zeigt globalThis nicht direkt auf das Globale, es gibt eine Indirektion. Betrachten Sie als Beispiel einen iframe auf einer Webseite

Datei 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>

Datei iframe.html

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

Wie stellen Browser sicher, dass globalThis in diesem Szenario nicht geändert wird? Sie unterscheiden intern zwei Objekte

In Browsern verweist globalThis auf den WindowProxy; überall sonst verweist es direkt auf das globale Objekt.

5.5 Die globale Umgebung

Der globale Scope ist der „äußerste“ Scope – er hat keinen äußeren Scope. Seine Umgebung ist die globale Umgebung. Jede Umgebung ist mit der globalen Umgebung über eine Kette von Umgebungen verbunden, die durch Verweise auf äußere Umgebungen verbunden sind. Der Verweis auf die äußere Umgebung der globalen Umgebung ist null.

Der globale Umgebungs-Record verwendet zwei Umgebungs-Records zur Verwaltung seiner Variablen

Welcher dieser beiden Records wann verwendet wird, wird bald erklärt.

5.5.1 Skript-Scope und Modul-Scopes

In JavaScript befinden wir uns nur im globalen Scope auf den obersten Ebenen von Skripten. Im Gegensatz dazu hat jedes Modul seinen eigenen Scope, der ein Unter-Scope des Skript-Scopes ist.

Wenn wir die relativ komplizierten Regeln ignorieren, wie Variablen-Bindings zur globalen Umgebung hinzugefügt werden, dann funktionieren der globale Scope und die Modul-Scopes so, als wären sie verschachtelte Codeblöcke

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

  // (Global variables)

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

5.5.2 Variablen erstellen: Deklaratives Record vs. Objekt-Record

Um eine wirklich globale Variable zu erstellen, müssen wir uns im globalen Scope befinden – was nur auf der obersten Ebene von Skripten der Fall ist

<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 Variablen abrufen oder setzen

Wenn wir eine Variable abrufen oder setzen und beide Umgebungs-Records eine Bindung für diese Variable haben, dann gewinnt der deklarative Record

<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 Globale ECMAScript-Variablen und globale Host-Variablen

Zusätzlich zu den über var und Funktionsdeklarationen erstellten Variablen enthält das globale Objekt die folgenden Eigenschaften

Die Verwendung von const oder let garantiert, dass globale Variablendeklarationen die eingebauten globalen Variablen von ECMAScript und der Host-Plattform nicht beeinflussen (oder von ihnen beeinflusst werden).

Zum Beispiel haben Browser die globale Variable .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';

Wenn eine Variable bereits existiert (wie in diesem Fall location), dann verhält sich eine var-Deklaration mit einem Initialisierer wie eine Zuweisung. Deshalb geraten wir in diesem Beispiel in Schwierigkeiten.

Beachten Sie, dass dies nur im globalen Scope ein Problem darstellt. In Modulen befinden wir uns nie im globalen Scope (es sei denn, wir verwenden eval() oder ähnliches).

Abb. 10 fasst alles zusammen, was wir in diesem Abschnitt gelernt haben.

Abbildung 10: Die Umgebung für den globalen Scope verwaltet ihre Bindings über einen globalen Umgebungs-Record, der wiederum auf zwei Umgebungs-Records basiert: einem Objekt-Umgebungs-Record, dessen Bindings im globalen Objekt gespeichert werden, und einem deklarativen Umgebungs-Record, der einen internen Speicher für seine Bindings verwendet. Globale Variablen können daher durch Hinzufügen von Eigenschaften zum globalen Objekt oder über verschiedene Deklarationen erstellt werden. Das globale Objekt wird mit den eingebauten globalen Variablen von ECMAScript und der Host-Plattform initialisiert. Jedes ECMAScript-Modul hat seine eigene Umgebung, deren äußere Umgebung die globale Umgebung ist.

5.6 Fazit: Warum hat JavaScript sowohl normale globale Variablen als auch das globale Objekt?

Das globale Objekt wird generell als Fehler angesehen. Aus diesem Grund erstellen neuere Konstrukte wie const, let und Klassen normale globale Variablen (wenn sie sich im Skript-Scope befinden).

Glücklicherweise lebt der Großteil des in modernem JavaScript geschriebenen Codes in ECMAScript-Modulen und CommonJS-Modulen. Jedes Modul hat seinen eigenen Scope, weshalb die Regeln für globale Variablen bei Modul-basiertem Code selten eine Rolle spielen.

5.7 Weiterführende Lektüre und Quellen für dieses Kapitel

Umgebungen und das globale Objekt in der ECMAScript-Spezifikation

globalThis:

Das globale Objekt in Browsern