Benannte Element-IDs können als globale JavaScript-Variablen referenziert werden

Avatar of Matteo Mazzarolo
Matteo Mazzarolo am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Wussten Sie, dass DOM-Elemente mit IDs in JavaScript als globale Variablen zugänglich sind? Es ist eines dieser Dinge, die schon ewig existieren, aber ich beschäftige mich zum ersten Mal wirklich damit.

Wenn Sie dies zum ersten Mal hören, halten Sie sich fest! Wir können es in Aktion sehen, indem wir einfach eine ID zu einem Element in HTML hinzufügen

<div id="cool"></div>

Normalerweise würden wir eine neue Variable mit querySelector("#cool") oder getElementById("cool") definieren, um dieses Element auszuwählen

var el = querySelector("#cool");

Aber wir haben tatsächlich bereits Zugriff auf #cool ohne diesen Aufwand

Daher kann jede id – oder auch jedes name-Attribut – in HTML über JavaScript mit window[ELEMENT_ID] aufgerufen werden. Auch das ist nicht gerade „neu“, aber es ist wirklich ungewöhnlich, es zu sehen.

Wie Sie sich vielleicht denken können, ist der Zugriff auf den globalen Geltungsbereich mit benannten Referenzen nicht die beste Idee. Manche Leute nennen das auch den „global scope polluter“. Wir werden darauf eingehen, warum das so ist, aber zuerst...

Einige Kontexte

Dieser Ansatz ist in der HTML-Spezifikation dargelegt, wo er als „benannter Zugriff auf das Window-Objekt“ beschrieben wird.

Internet Explorer war der Erste, der das Feature implementierte. Alle anderen Browser haben es ebenfalls hinzugefügt. Gecko war zu dieser Zeit der einzige Browser, der es nicht direkt im Standardmodus unterstützte, sondern es stattdessen als experimentelles Feature behandelte. Es gab Zögern, es überhaupt zu implementieren, aber es wurde im Namen der Browserkompatibilität vorangetrieben (Gecko versuchte sogar, WebKit zu überzeugen, es aus dem Standardmodus zu entfernen) und fand schließlich im Standardmodus in Firefox 14 seinen Weg.

Eine Sache, die vielleicht nicht allgemein bekannt ist, ist, dass die Browser einige Vorsichtsmaßnahmen ergreifen mussten – mit unterschiedlichem Erfolg –, um sicherzustellen, dass generierte Globale die Webseite nicht beschädigen. Eine solche Maßnahme ist...

Variablenüberschattung

Das wahrscheinlich Interessanteste an diesem Feature ist, dass benannte Elementreferenzen keine bestehenden globalen Variablen überschatten. Wenn also ein DOM-Element eine id hat, die bereits als globale Variable definiert ist, wird die bestehende nicht überschrieben. Zum Beispiel

<head>
  <script>
    window.foo = "bar";
  </script>
</head>
<body>
  <div id="foo">I won't override window.foo</div>
  <script>
    console.log(window.foo); // Prints "bar"
  </script>
</body>

Und umgekehrt gilt das Gleiche.

<div id="foo">I will be overridden :(</div>
<script>
  window.foo = "bar";
  console.log(window.foo); // Prints "bar"
</script>

Dieses Verhalten ist unerlässlich, da es gefährliche Überschreibungen wie <div id="alert" /> zunichte macht, was andernfalls zu einem Konflikt führen und die alert-API ungültig machen würde. Diese Schutzmaßnahme ist vielleicht der Grund, warum Sie – wenn Sie wie ich sind – zum ersten Mal davon erfahren.

Das Argument gegen benannte globale Variablen

Vorhin sagte ich, dass die Verwendung globaler benannter Elemente als Referenzen vielleicht nicht die beste Idee ist. Dafür gibt es viele Gründe, die TJ VanToll auf seinem Blog schön zusammengefasst hat und die ich hier kurz zusammenfassen werde

  • Wenn sich das DOM ändert, ändert sich auch die Referenz. Das führt zu sehr „brüchigem“ Code (der Begriff der Spezifikation dafür), bei dem die Trennung der Zuständigkeiten zwischen HTML und JavaScript zu stark sein könnte.
  • Versehentliche Referenzen sind viel zu einfach. Ein einfacher Tippfehler kann sehr wohl eine benannte globale Variable referenzieren und zu unerwarteten Ergebnissen führen.
  • Es ist in verschiedenen Browsern unterschiedlich implementiert. Wir sollten beispielsweise auf einen Link mit einer id zugreifen können – z. B. <a id="cool"> – aber einige Browser (namentlich Safari und Firefox) geben eine ReferenceError in der Konsole zurück.
  • Es liefert möglicherweise nicht das, was Sie erwarten. Laut Spezifikation sollte der Browser eine HTMLCollection mit einem Array der Instanzen zurückgeben, wenn mehrere Instanzen desselben benannten Elements im DOM vorhanden sind – sagen wir, zwei Instanzen von <div class="cool">. Firefox gibt jedoch nur die erste Instanz zurück. Andererseits sagt die Spezifikation, dass wir ohnehin nur eine Instanz einer id im Baum eines Elements verwenden sollten. Aber das hindert die Seite nicht daran zu funktionieren oder so etwas.
  • Vielleicht gibt es Leistungskosten? Ich meine, der Browser muss diese Liste von Referenzen erstellen und pflegen. Ein paar Leute haben in diesem StackOverflow-Thread Tests durchgeführt, bei denen benannte globale Variablen tatsächlich in einem Test performanter und in einem neueren Test weniger performant waren.

Zusätzliche Überlegungen

Nehmen wir an, wir werfen die Kritikpunkte gegen die Verwendung benannter globaler Variablen über Bord und verwenden sie trotzdem. Das ist in Ordnung. Aber es gibt ein paar Dinge, die Sie vielleicht bedenken sollten, wenn Sie es tun.

Polyfills

So seltsam es auch klingen mag, diese Art von globalen Prüfungen sind eine typische Einrichtungsvoraussetzung für Polyfills. Betrachten Sie das folgende Beispiel, bei dem wir einen Cookie mit der neuen CookieStore API setzen und ihn für Browser, die ihn noch nicht unterstützen, polyfillen

<body>
  <img id="cookieStore"></img>
  <script>
    // Polyfill the CookieStore API if not yet implemented.
    // https://developer.mozilla.org/en-US/docs/Web/API/CookieStore
    if (!window.cookieStore) {
      window.cookieStore = myCookieStorePolyfill;
    }
    cookieStore.set("foo", "bar");
  </script>
</body>

Dieser Code funktioniert in Chrome einwandfrei, wirft aber in Safari folgenden Fehler.

TypeError: cookieStore.set is not a function

Safari unterstützt die CookieStore-API zum Zeitpunkt der Erstellung dieses Artikels nicht. Infolgedessen wird der Polyfill nicht angewendet, da die ID des img-Elements eine globale Variable erstellt, die mit der globalen Variable cookieStore kollidiert.

JavaScript-API-Updates

Wir können die Situation umdrehen und ein weiteres Problem finden, bei dem Updates der JavaScript-Engine des Browsers globale Referenzen eines benannten Elements brechen können.

Zum Beispiel:

<body>
  <input id="BarcodeDetector"></input>
  <script>
    window.BarcodeDetector.focus();
  </script>
</body>

Dieses Skript holt sich eine Referenz auf das Eingabeelement und ruft focus() darauf auf. Es funktioniert korrekt. Dennoch wissen wir nicht, wie lange es weiterhin funktionieren wird.

Die globale Variable, die wir verwenden, um auf das Eingabeelement zu verweisen, wird aufhören zu funktionieren, sobald Browser die BarcodeDetector API unterstützen. Zu diesem Zeitpunkt wird die globale Variable window.BarcodeDetector keine Referenz mehr auf das Eingabeelement sein und .focus() wird einen Fehler auslösen: „window.BarcodeDetector.focus ist keine Funktion“.

Fazit

Fassen wir zusammen, wie wir hierher gekommen sind

  • Alle wichtigen Browser erstellen automatisch globale Referenzen zu jedem DOM-Element mit einer id (oder in einigen Fällen einem name-Attribut).
  • Der Zugriff auf diese Elemente über ihre globalen Referenzen ist unzuverlässig und potenziell gefährlich. Verwenden Sie stattdessen querySelector oder getElementById.
  • Da globale Referenzen automatisch generiert werden, können sie Nebenwirkungen auf Ihren Code haben. Das ist ein guter Grund, das id-Attribut nur zu verwenden, wenn Sie es wirklich brauchen.

Am Ende des Tages ist es wahrscheinlich eine gute Idee, benannte globale Variablen in JavaScript zu vermeiden. Ich habe die Spezifikation bereits zitiert, wie sie zu „brüchigem“ Code führt, aber hier ist der vollständige Text, um den Punkt zu verdeutlichen

Als allgemeine Regel führt die Berufung darauf zu brüchigem Code. Welche IDs auf diese API abgebildet werden, kann sich im Laufe der Zeit ändern, wenn beispielsweise neue Features zur Webplattform hinzugefügt werden. Verwenden Sie stattdessen document.getElementById() oder document.querySelector().

Ich denke, die Tatsache, dass die HTML-Spezifikation selbst empfiehlt, von diesem Feature Abstand zu nehmen, spricht für sich.