Do you know that DOM components with IDs are accessible in JavaScript as international variables? It’s a kind of issues that’s been round, like, perpetually however I’m actually digging into it for the primary time.
If that is the primary time you’re listening to about it, brace your self! We will see it in motion just by including an ID to a component in HTML:
<div id="cool"></div>
Usually, we’d outline a brand new variable utilizing querySelector("#cool")
or getElementById("cool")
to pick that aspect:
var el = querySelector("#cool");
However we truly have already got entry to #cool
with out that rigamorale:
So, any id
— or identify
attribute, for that matter — within the HTML could be accessed in JavaScript utilizing window[ELEMENT_ID]
. Once more, this isn’t precisely “new” however it’s actually unusual to see.
As chances are you’ll guess, accessing the worldwide scope with named references isn’t the best thought. Some people have come to name this the “international scope polluter.” We’ll get into why that’s, however first…
Some context
This strategy is outlined within the HTML specification, the place it’s described as “named entry on the Window
object.”
Web Explorer was the primary to implement the function. All different browsers added it as nicely. Gecko was the one browser on the time to not help it straight in requirements mode, opting as a substitute to make it an experimental function. There was hesitation to implement it in any respect, however it moved forward within the identify of browser compatibility (Gecko even tried to persuade WebKit to maneuver it out of requirements mode) and ultimately made it to requirements mode in Firefox 14.
One factor that may not be well-known is that browsers needed to put in place a couple of precautionary measures — with various levels of success — to make sure generated globals don’t break the webpage. One such measure is…
Variable shadowing
In all probability essentially the most fascinating a part of this function is that named aspect references don’t shadow present international variables. So, if a DOM aspect has an id
that’s already outlined as a world, it received’t override the prevailing one. For instance:
<head>
<script>
window.foo = "bar";
</script>
</head>
<physique>
<div id="foo">I will not override window.foo</div>
<script>
console.log(window.foo); // Prints "bar"
</script>
</physique>
And the alternative is true as nicely:
<div id="foo">I might be overridden :(</div>
<script>
window.foo = "bar";
console.log(window.foo); // Prints "bar"
</script>
This conduct is crucial as a result of it nullifies harmful overrides comparable to <div id="alert" />
, which might in any other case create a battle by invalidating the alert
API. This safeguarding approach could very nicely be the why you — for those who’re like me — are studying about this for the primary time.
The case towards named globals
Earlier, I stated that utilizing international named components as references won’t be the best thought. There are many causes for that, which TJ VanToll has coated properly over at his weblog and I’ll summarize right here:
- If the DOM modifications, then so does the reference. That makes for some actually “brittle” (the spec’s time period for it) code the place the separation of issues between HTML and JavaScript is perhaps an excessive amount of.
- Unintended references are far too simple. A easy typo could very nicely wind up referencing a named international and offer you sudden outcomes.
- It’s applied in a different way in browsers. For instance, we must always have the ability to entry an anchor with an
id
— e.g.<a id="cool">
— however some browsers (specifically Safari and Firefox) return aReferenceError
within the console. - It won’t return what you assume. In keeping with the spec, when there are a number of situations of the identical named aspect within the DOM — say, two situations of
<div class="cool">
— the browser ought to return anHTMLCollection
with an array of the situations. Firefox, nevertheless, solely returns the primary occasion. Then once more, the spec says we ought to make use of one occasion of anid
in a component’s tree anyway. However doing so received’t cease a web page from working or something like that. - Possibly there’s a efficiency price? I imply, the browser’s gotta make that checklist of references and keep it. A few people ran exams on this StackOverflow thread, the place named globals have been truly extra performant in a single take a look at and much less performant in a newer take a look at.
Further concerns
Let’s say we chuck the criticisms towards utilizing named globals and use them anyway. It’s all good. However there are some stuff you would possibly need to contemplate as you do.
Polyfills
As edge-case-y as it might sound, a majority of these international checks are a typical setup requirement for polyfills. Take a look at the next instance the place we set a cookie utilizing the brand new CookieStore
API, polyfilling it on browsers that don’t help it but:
<physique>
<img id="cookieStore"></img>
<script>
// Polyfill the CookieStore API if not but applied.
// https://developer.mozilla.org/en-US/docs/Internet/API/CookieStore
if (!window.cookieStore) {
window.cookieStore = myCookieStorePolyfill;
}
cookieStore.set("foo", "bar");
</script>
</physique>
This code works completely superb in Chrome, however throws the next error in Safari.:
TypeError: cookieStore.set isn't a operate
Safari lacks help for the CookieStore
API as of this writing. Consequently, the polyfill isn’t utilized as a result of the img
aspect ID creates a world variable that clashes with the cookieStore
international.
JavaScript API updates
We will flip the scenario and discover yet one more situation the place updates to the browser’s JavaScript engine can break a named aspect’s international references.
For instance:
<physique>
<enter id="BarcodeDetector"></enter>
<script>
window.BarcodeDetector.focus();
</script>
</physique>
That script grabs a reference to the enter aspect and invokes focus()
on it. It really works appropriately. Nonetheless, we don’t know the way lengthy it would proceed to work.
You see, the worldwide variable we’re utilizing to reference the enter aspect will cease working as quickly as browsers begin supporting the BarcodeDetector
API. At that time, the window.BarcodeDetector
international will not be a reference to the enter aspect and .focus()
will throw a “window.BarcodeDetector.focus
isn’t a operate” error.
Conclusion
Let’s sum up how we bought right here:
- All main browsers robotically create international references to every DOM aspect with an
id
(or, in some instances, aidentify
attribute). - Accessing these components by their international references is unreliable and probably harmful. Use
querySelector
orgetElementById
as a substitute. - Since international references are generated robotically, they could have some uncomfortable side effects in your code. That’s an excellent purpose to keep away from utilizing the
id
attribute until you actually need it.
On the finish of the day, it’s most likely a good suggestion to keep away from utilizing named globals in JavaScript. I quoted the spec earlier about the way it results in “brittle” code, however right here’s the complete textual content to drive the purpose residence:
As a basic rule, counting on it will result in brittle code. Which IDs find yourself mapping to this API can range over time, as new options are added to the net platform, for instance. As a substitute of this, use
doc.getElementById()
ordoc.querySelector()
.
I believe the truth that the HTML spec itself recommends to staying away from this function speaks for itself.