Do you know that DOM parts with IDs are accessible in JavaScript as world variables? It’s a kind of issues that’s been round, like, eternally 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 ingredient:
var el = querySelector("#cool");
However we truly have already got entry to #cool
with out that rigamorale:
So, any id
— or title
attribute, for that matter — within the HTML may be accessed in JavaScript utilizing window[ELEMENT_ID]
. Once more, this isn’t precisely “new” however it’s actually unusual to see.
As you could guess, accessing the worldwide scope with named references isn’t the best thought. Some of us have come to name this the “world scope polluter.” We’ll get into why that’s, however first…
Some context
This method 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 characteristic. All different browsers added it as nicely. Gecko was the one browser on the time to not help it immediately in requirements mode, opting as a substitute to make it an experimental characteristic. There was hesitation to implement it in any respect, however it moved forward within the title 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 number of precautionary measures — with various levels of success — to make sure generated globals don’t break the webpage. One such measure is…
Variable shadowing
Most likely essentially the most fascinating a part of this characteristic is that named ingredient references don’t shadow current world variables. So, if a DOM ingredient has an id
that’s already outlined as a worldwide, 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 will likely be overridden :(</div>
<script>
window.foo = "bar";
console.log(window.foo); // Prints "bar"
</script>
This conduct is important as a result of it nullifies harmful overrides equivalent 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 world named parts as references won’t be the best thought. There are many causes for that, which TJ VanToll has lined 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 considerations between HTML and JavaScript is likely to be an excessive amount of.
- Unintentional references are far too simple. A easy typo could very nicely wind up referencing a named world and offer you sudden outcomes.
- It’s applied in another way in browsers. For instance, we should always be capable of entry an anchor with an
id
— e.g.<a id="cool">
— however some browsers (particularly Safari and Firefox) return aReferenceError
within the console. - It won’t return what you assume. In line with the spec, when there are a number of situations of the identical named ingredient 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. - Perhaps there’s a efficiency value? I imply, the browser’s gotta make that listing of references and preserve it. A few of us ran checks on this StackOverflow thread, the place named globals have been truly extra performant in a single check and much less performant in a more moderen check.
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 belongings you would possibly need to contemplate as you do.
Polyfills
As edge-case-y as it might sound, these kind of world 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/Net/API/CookieStore
if (!window.cookieStore) {
window.cookieStore = myCookieStorePolyfill;
}
cookieStore.set("foo", "bar");
</script>
</physique>
This code works completely positive in Chrome, however throws the next error in Safari.:
TypeError: cookieStore.set is just not a operate
Safari lacks help for the CookieStore
API as of this writing. Consequently, the polyfill is just not utilized as a result of the img
ingredient ID creates a worldwide variable that clashes with the cookieStore
world.
JavaScript API updates
We will flip the state of affairs and discover one more challenge the place updates to the browser’s JavaScript engine can break a named ingredient’s world references.
For instance:
<physique>
<enter id="BarcodeDetector"></enter>
<script>
window.BarcodeDetector.focus();
</script>
</physique>
That script grabs a reference to the enter ingredient and invokes focus()
on it. It really works accurately. Nonetheless, we don’t know the way lengthy it can proceed to work.
You see, the worldwide variable we’re utilizing to reference the enter ingredient will cease working as quickly as browsers begin supporting the BarcodeDetector
API. At that time, the window.BarcodeDetector
world will not be a reference to the enter ingredient and .focus()
will throw a “window.BarcodeDetector.focus
is just not a operate” error.
Bonus: Not all named parts generate world references
Need to hear one thing humorous? So as to add insult to the damage, named parts are accessible as world variables provided that the names comprise nothing however letter. Browsers received’t create a worldwide reference for a component with a ID that accommodates particular characters and numbers, like hello-world
and item1
.
Conclusion
Let’s sum up how we obtained right here:
- All main browsers routinely create world references to every DOM ingredient with an
id
(or, in some circumstances, atitle
attribute). - Accessing these parts by way of their world references is unreliable and doubtlessly harmful. Use
querySelector
orgetElementById
as a substitute. - Since world references are generated routinely, they could have some unintended effects in your code. That’s a great motive to keep away from utilizing the
id
attribute until you really want it.
On the finish of the day, it’s in all probability 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 dwelling:
As a normal 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 an alternative of this, use
doc.getElementById()
ordoc.querySelector()
.
I believe the truth that the HTML spec itself recommends to staying away from this characteristic speaks for itself.