Wist u dat DOM-elementen met ID's in JavaScript toegankelijk zijn als globale variabelen? Het is een van die dingen die er altijd al zijn geweest, maar ik ben er echt voor het eerst in aan het graven.
Als dit de eerste keer is dat je erover hoort, zet je dan schrap! We kunnen het in actie zien door simpelweg een ID toe te voegen aan een element in HTML:
Normaal gesproken definiรซren we een nieuwe variabele met querySelector("#cool")
or getElementById("cool")
om dat element te selecteren:
var el = querySelector("#cool");
Maar we hebben eigenlijk al toegang tot #cool
zonder die rigamorale:
Dus, elke id
- of name
attribuut, wat dat betreft โ in de HTML is toegankelijk in JavaScript met behulp van window[ELEMENT_ID]
. Nogmaals, dit is niet echt "nieuw", maar het is echt ongewoon om te zien.
Zoals je misschien wel vermoedt, is toegang tot het globale bereik met benoemde verwijzingen niet het beste idee. Sommige mensen zijn dit de 'global scope-vervuiler' gaan noemen. We zullen ingaan op waarom dat zo is, maar eerst...
Enige context
Deze aanpak is beschreven in de HTML-specificatie, waar het wordt beschreven als "named toegang op de" Window
object."
Internet Explorer was de eerste die de functie implementeerde. Alle andere browsers hebben het ook toegevoegd. Gecko was destijds de enige browser die het niet rechtstreeks in de standaardmodus ondersteunde, maar er in plaats daarvan voor koos om er een experimentele functie van te maken. Er was enige aarzeling om het uit te voeren, maar het is ging vooruit in naam van browsercompatibiliteit (Gecko probeerde zelfs om overtuig WebKit om het uit de standaardmodus te halen) en bereikte uiteindelijk de standaardmodus in Firefox 14.
Een ding dat misschien niet goed bekend is, is dat browsers een paar voorzorgsmaatregelen moesten nemen - met wisselend succes - om ervoor te zorgen dat gegenereerde globals de webpagina niet breken. Een van die maatregelen isโฆ
Variabele schaduwwerking
Waarschijnlijk het meest interessante aan deze functie is dat verwijzingen naar benoemde elementen dat niet doen schaduw bestaande globale variabelen. Dus, als een DOM-element een . heeft id
die al is gedefinieerd als een globaal, zal de bestaande niet overschrijven. Bijvoorbeeld:
window.foo = "bar";
I won't override window.foo
console.log(window.foo); // Prints "bar"
En het tegenovergestelde is ook waar:
I will be overridden :(
window.foo = "bar";
console.log(window.foo); // Prints "bar"
Dit gedrag is essentieel omdat het gevaarlijke overrides teniet doet, zoals:
, die anders een conflict zou veroorzaken door het ongeldig maken van de alert
API. Deze beveiligingstechniek is misschien wel de reden waarom je - als je op mij lijkt - hier voor het eerst over leert.
De zaak tegen genoemde globals
Eerder zei ik dat het misschien niet het beste idee is om globale benoemde elementen als referenties te gebruiken. Daar zijn veel redenen voor, die TJ VanToll heeft mooi verslag gedaan op zijn blog en ik zal het hier samenvatten:
- Als de DOM verandert, verandert ook de referentie. Dat maakt sommige echt "bros" (de spec's term for it) code waar de scheiding van zorgen tussen HTML en JavaScript te veel zou kunnen zijn.
- Toevallige verwijzingen zijn veel te gemakkelijk. Een simpele typefout kan heel goed eindigen met het verwijzen naar een genoemde globale en onverwachte resultaten opleveren.
- Het wordt anders geรฏmplementeerd in browsers. We zouden bijvoorbeeld toegang moeten hebben tot een anker met een
id
- bijvโ maar sommige browsers (namelijk Safari en Firefox) retourneren a
ReferenceError
in de console. - Het geeft misschien niet terug wat je denkt. Volgens de specificatie, wanneer er meerdere exemplaren van hetzelfde benoemde element in de DOM zijn, bijvoorbeeld twee exemplaren van
โ de browser zou een . moeten teruggeven
HTMLCollection
met een array van de instanties. Firefox retourneert echter alleen de eerste instantie. Nogmaals, de specificatie zegt: we zouden รฉรฉn instantie van an . moeten gebruikenid
in ieder geval in de boom van een element. Maar dit zal niet voorkomen dat een pagina werkt of iets dergelijks. - Misschien zijn er prestatiekosten? Ik bedoel, de browser moet die lijst met referenties maken en onderhouden. Een paar mensen hebben tests uitgevoerd in deze StackOverflow-thread, waar benoemde globals eigenlijk waren beter presteren in รฉรฉn test en minder presterend in een recentere test.
Aanvullende overwegingen
Laten we zeggen dat we de kritiek op het gebruik van benoemde globals weggooien en ze toch gebruiken. Het is al goed. Maar er zijn een aantal dingen die u misschien wilt overwegen als u dat doet.
Polyvullingen
Hoe scherp het ook klinkt, dit soort globale controles zijn een typische instellingsvereiste voor polyfills. Bekijk het volgende voorbeeld waarin we een cookie plaatsen met behulp van de nieuwe CookieStore
API, polyfilling in browsers die het nog niet ondersteunen:
// 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");
Deze code werkt prima in Chrome, maar geeft de volgende foutmelding in Safari.:
TypeError: cookieStore.set is not a function
Safari heeft geen ondersteuning voor de CookieStore
API op het moment van schrijven. Als gevolg hiervan wordt de polyfill niet toegepast omdat de img
element ID creรซert een globale variabele die botst met de cookieStore
global.
JavaScript API-updates
We kunnen de situatie omdraaien en nog een ander probleem vinden waarbij updates van de JavaScript-engine van de browser de globale verwijzingen van een benoemd element kunnen verbreken.
Bijvoorbeeld:
window.BarcodeDetector.focus();
Dat script grijpt een verwijzing naar het invoerelement en roept focus()
ben ermee bezig. Het werkt correct. Toch weten we niet hoe lang het zal blijven werken.
Zie je, de globale variabele die we gebruiken om naar het invoerelement te verwijzen, zal stoppen met werken zodra browsers de . gaan ondersteunen BarcodeDetector
API. Op dat moment is de window.BarcodeDetector
global zal niet langer een verwijzing zijn naar het invoerelement en .focus()
zal een "window.BarcodeDetector.focus
is geen functieโ fout.
Bonus: niet alle benoemde elementen genereren globale referenties
Wil je iets grappigs horen? Om het nog erger te maken, zijn benoemde elementen alleen toegankelijk als globale variabelen als de namen niets anders dan letters bevatten. Browsers maken geen globale referentie voor een element met een ID die speciale tekens en cijfers bevat, zoals hello-world
en item1
.
Conclusie
Laten we samenvatten hoe we hier zijn gekomen:
- Alle belangrijke browsers creรซren automatisch globale verwijzingen naar elk DOM-element met een
id
(of, in sommige gevallen, eenname
attribuut). - Toegang tot deze elementen via hun wereldwijde referenties is onbetrouwbaar en potentieel gevaarlijk. Gebruiken
querySelector
orgetElementById
gebruiken. - Aangezien globale verwijzingen automatisch worden gegenereerd, kunnen ze enkele bijwerkingen hebben op uw code. Dat is een goede reden om het gebruik van de
id
attribuut tenzij je het echt nodig hebt.
Uiteindelijk is het waarschijnlijk een goed idee om het gebruik van benoemde globals in JavaScript te vermijden. Ik citeerde eerder de specificatie over hoe het leidt tot "brosse" code, maar hier is de volledige tekst om het punt naar huis te brengen:
Als algemene regel geldt dat het vertrouwen hierop zal leiden tot broze code. Welke ID's uiteindelijk aan deze API worden toegewezen, kan in de loop van de tijd variรซren, bijvoorbeeld omdat er nieuwe functies aan het webplatform worden toegevoegd. Gebruik in plaats hiervan
document.getElementById()
ordocument.querySelector()
.
Ik denk dat het feit dat de HTML-specificatie zelf aanbeveelt om weg te blijven van deze functie voor zich spreekt.