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
idzugreifen können – z. B.<a id="cool">– aber einige Browser (namentlich Safari und Firefox) geben eineReferenceErrorin der Konsole zurück. - Es liefert möglicherweise nicht das, was Sie erwarten. Laut Spezifikation sollte der Browser eine
HTMLCollectionmit 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 eineridim 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 einemname-Attribut). - Der Zugriff auf diese Elemente über ihre globalen Referenzen ist unzuverlässig und potenziell gefährlich. Verwenden Sie stattdessen
querySelectorodergetElementById. - 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()oderdocument.querySelector().
Ich denke, die Tatsache, dass die HTML-Spezifikation selbst empfiehlt, von diesem Feature Abstand zu nehmen, spricht für sich.
Das Feature scheint für Elemente innerhalb des Shadow DOM nicht zu funktionieren.
Super interessant! Ich bin vor einiger Zeit darauf gestoßen, hatte aber keine Ahnung, was passierte. Wir hatten ein Skript-Tag, das so aussah:
Aber jedes Mal, wenn ich versuchte, auf
window.configzuzugreifen, bekam ich die Skript-Tag-Referenz und NICHT das Inline-Objekt. Ich hatte keine Ahnung, was vor sich ging, und habe am Ende dasid-Attribut entfernt (da es nichts bewirkte). Gut zu wissen, dass es zumindest eine Logik gab, warum das passierte.Hallo Ben! :D
Seltsam!
window.confighätte in Ihrem Beispiel funktionieren sollen. Ich frage mich, was es gewesen sein könnte.Was? Es gibt nichts Besonderes an bestimmten Tag-Namen, und jeder Browser, bei dem das nicht funktionierte, würde die Spezifikation verletzen. Sicherlich funktioniert es in Firefox einwandfrei. Ich habe keine Ahnung, woher das kommt.
Tatsächlich habe ich letzte Woche eine klare Verletzung der Spezifikation bei diesem Feature gefunden (die ich bisher nicht eingereicht habe): Chromium implementiert es nicht, wenn das Dokument mit XML-Syntax geladen wird. Diese erste URL wirft also eine
ReferenceErrorin Chromium (im Gegensatz zu Firefox oder beiden in der zweiten URL)Insgesamt ist es einer meiner bevorzugten aggressiven Minifizierungs-/Golf-Tricks (und ich benutze ihn regelmäßig in One-Off-Skripten für meinen eigenen Gebrauch), aber es ist nicht am robustesten.
Das ist absolut falsch.
window["hello-world"]unditem1werden beide einwandfrei funktionieren. (Beweis: Gehen Sie zudata:text/html,<p id=hello-world><p id=item1>und probieren Sie es aus.)Dies und einige andere Dinge im Artikel deuten darauf hin, dass der Autor nicht verstanden hat, wie das Ganze tatsächlich implementiert ist (was zugegebenermaßen aus der Spezifikation nicht allzu klar hervorgeht, wenn man das prototypische Vererbungsmodell von JavaScript oder die Natur des globalen
window-Objekts nicht versteht). Ohne ins Detail zu gehen: Es ist nicht der Browser, der Eigenschaften setzt, wenn Sie einem Element eine solche ID geben, sondern der Fallback, sodass, wenn Sie versuchen, auf eine Eigenschaft zuzugreifen, die auf dem globalen Objekt nicht existiert, er versucht, ein geeignetes Objekt zu finden, anstatt sofortundefinedzu erhalten.Ja, dieser Bonuspunt sollte nicht durchrutschen – mein Fehler. Danke für die Meldung! Ich werde auch überprüfen, was Sie in dem anderen Kommentar erwähnt haben.
Nein, er hat Recht, es funktioniert nicht, probieren Sie es selbst in Chrome aus, Mann.
Zusätzlicher „Spaßfakt“: Der gleiche Mechanismus ist auf der HTMLFormElement-Schnittstelle verfügbar, d. h. auf allen
<form>-Knoten. Wenn Sie Pech haben und einem Button oder Eingabeelement die IDsubmitgeben und dann versuchen,form.submit()aufzurufen, erleben Sie eine böse Überraschung.Ah, die süße Erbschaft von Microsofts IE document.all Objektmodell. Schade, dass wir uns nicht davon getrennt haben, als wir die Chance hatten...
Ich musste einen Workaround dafür schreiben:
Ziemlich hässlicher Code, ich weiß. Das ist das Bookmarklet, das ich verwende, um einige schlecht implementierte Client-seitige E-Mail-Filter in WordPress-Kommentaren zu umgehen.
Ich frage mich, was in Ihrem Fall tatsächlich passiert ist, denn laut dem Blogbeitrag sollte der Wert von window.config das Inline-Objekt sein.
Alte Programmierer werden sich erinnern, dass die Verwendung von IDs als globale Variablen die dominierende Art der Programmierung in JavaScript mit Internet Explorer war. Andere Browser weigerten sich, dies zu unterstützen. Die gesamte Branche musste alle JavaScript-Anwendungen umschreiben, um stattdessen getElementById() zu verwenden, da dies der standardkonforme Weg war.
Aber jetzt, wo wir hier sind, gibt es keinen Grund, zum alten Weg zurückzukehren.
Ich habe Mühe zu verstehen, was „Es liefert möglicherweise nicht das, was Sie erwarten.“ bedeutet.
Sollte dort nicht stehen:
<div id="cool">anstatt „class“? Ich weiß, dass Sie das nicht tun würden, aber das scheint Ihre Aussage zu sein?Leider existiert dieses Problem/Feature auch für ES6-Module.
Das ist eigentlich ein tolles Feature!
Bezüglich dieses Beispiels im Artikel
Eine Möglichkeit, dies zu beheben, wäre, das
id-Attribut vom<img>-Element zu entfernen. Aber was, wenn Sie das nicht tun können? Gibt es eine Möglichkeit, das JavaScript so zu korrigieren, dass es gegen die Existenz eines Elements mit demid-AttributwertcookieStorewiderstandsfähig ist?Das ist cool, aber die Verwendung von IDs ist an sich schon umstritten. Es hat die gleichen Nachteile wie jedes andere Singleton: Man denkt, das Objekt sei einzigartig und wird es für immer sein, dann sieht man sich plötzlich gezwungen, seinen Code neu zu schreiben. Ich ziehe es vor, Elemente als Sammlung nach Klassennamen abzufragen – so wie jQuery es tut.