<div id=result></div>
<script>
document.getElementById("result").textContent = "Why do it this way—";
document.querySelector("result").textContent = "—or even this way—";
result.textContent = "—when you can do it this way?";
</script>
Edit: adding another similar test to this page, window[`test${i}`] is taking roughly twice as long as document.querySelector(`#test${i}`) in Firefox, but only half as long in Chromium—which is still a bit slower than document.getElementById(`test${i}`) in Chromium, and than window[`test${i}`] in Firefox.
I would seriously not recommend doing this, because it's terrible at communicating intent. Yes, it works, but someone else (if you're lucky) or you will be staring at a variable that seems to have come out of nowhere trying to figure out what's going on sooner or later. Especially if the HTML id inadvertently got changed without changing the variable in kind
The spec appropriately points out that this is a fragile technique.
Also to avoid any doubt: if something’s called a golfing trick, you almost certainly shouldn’t use it on normal code.
I confess I use this technique on every page on my website, in my carefully-golfed light/dark mode switcher. (https://chrismorgan.info/blog/dark-theme-implementation/ has a slightly-expanded version of it, and uses a more sensible document.querySelector instead.)
You could also crash Internet Explorer 6 simply by including an element with id="tags" on the page. When the user chose to print the page out, the browser would try to access window.tags, find the element instead of what it was expecting to find, and give up.
Love this trick. It should be noted that the element is added to the window object as long as the id follows the syntax of a valid javascript variable (meaning no dashes).
2. It’s not added to the window object; rather, property lookup on a window object falls back to looking up elements by ID if there is no such property:
> typeof example
"undefined"
> document.body.id = "example";
> example
<body id="example">
> example = "no longer an element";
> example
"no longer an element"
> delete example;
true
> example
<body id="example">
one of? you have tricks better than this? this is so fucking spicy holy cow. how did i not know this? excuse me while i add this to all my future code for no good reason :).
They are both completely different and almost no one mentions how they differ in these comparison blog articles.
querySelector return a static node while getElementById returns a live node. If the element returned by getElementById is deleted in the DOM, the variable becomes unavailable while for querySelector you get a snapshot of the node that lives on.
If you use both of them the same way or don't know the difference, you are gonna have a bad time.
You are misreading the documentation. There's no such distinction as live node vs. static node in that sense. There's only a distinction between a live node list and a static node list. This is a difference between getElementsByClassName("foo") and querySelectorAll(".foo"), but not between getElementById("foo") and querySelector("#foo").
The difference is whether membership changes in the collection are reflected immediately. Changes to the nodes themselves are reflected as usual either way, and node references do not spookily invalidate or repoint themselves.
It's not part of javascript, it's part of the DOM API, and yes, that makes a difference. That's like calling C++ bad because you don't like boost, or calling python bad because you don't like django. (I am not saying tho that there isn't plenty to criticize in javascript itself).
The DOM API is not javascript specific. The API surface is defined in terms of WebIDL (Web Interface definition language). While it is most commonly found in browsers and therefore javascript, it is not specific to browsers or javascript. Browsers themselves ship various javascript environments that do not even include the DOM API at all (WebWorkers aka threads, ServiceWorkers, PAC all give you an environment without a DOM). nodejs doesn't come with a DOM (tho there is an implementation available in jsdom). There is nothing really preventing you from looking at the DOM spec and writing your own DOM API implementation for your preferred language. In fact Microsoft and Google used to do that for their in-browser support of vbscript and dart respectively, python ships with xml.dom (implementing an older version of the spec), java ships with org.w3c.dom (implementing an older version of the spec). There is a C# implementation in the AngleSharp library, somebody wrote a go implementation, and so on.
So tell me, how would the invalidation work if the API were written in a language like Rust?
You've just listed a bunch of GCed languages and told me it's due to the API not the language. GC lets you be sloppy with resource initialization/cleanup though, and having GC doesn't magically fix when the external objects you interact with go in and out of scope.
So what I'm arguing above is that GCed languages are actually just as hard as manual memory managed languages the moment you need to interact with the real world... be it UI elements, file handles, network sockets, or whatever. Memory isn't the only resource that needs management, and languages that have idiomatic resource management are actually good at this kind of thing.
No, it's a language issue. The language doesn't let you specify that things cannot go out of scope 'spookily', so it is ambiguous and you have to trust things like documentation. Just like it's easy to make memory management issues in C, this is the kind of thing that's easy to get wrong in JS.
My point it that it is a defined behaviour of the DOM API which is generally implemented in C++ and exposed to Javascript environment. It behave exactly the same way if you use bindings for any other language. It's not an ECMAScript defined feature.
You can also implement exactly this behaviour in any language so how is this then a Javascript language issue?
This is nonsense for querySelector and getElementById. They both query the current state of the DOM and return an Element or null. Variables can’t get deleted under you in JavaScript, so if you have a reference to a node that you remove from the DOM, it’s still the same node until you release all references and it gets garbage collected, or you put it back in the DOM.
What you’re talking about is only applicable or relevant for the methods that return collections. querySelectorAll returns a static NodeList, getElementsByName/getElementsByTagName/getElementsByClassName return live NodeLists or HTMLCollections.
> querySelector return a static node while getElementById returns a live node. If the element returned by getElementById is deleted in the DOM, the variable becomes unavailable while for querySelector you get a snapshot of the node that lives on.
It makes sense that that would be the case. You still have a reference in scope to the element, so the GC leaves it alone. I could see the described behavior as a bug in maybe an older engine, but so far as I can recall I've never run into it in practice.
Yeah, I surprised to even see those two terms on an article on HN (who uses getElementById or querySelectors nowadays right?) but I was even more surprised by the number of comments it gathered (which are of course unrelated to the original article)
That’s incorrect. Maybe you’re thinking of querySelectorAll, which returns a static list, compared to getElementsByTagName and getElementsByClassName, which return live ones?
I wonder why we need all these different collections. Makes the DOM feel hacked together by lots of totally different people not communicating (?) There's probably a reason, though.
Exactly. Once upon a time the powers that were figured a live node list would be cool, so they did that. Many moons later, the new powers that were decided that while a live node list looks cool, it contains too much magic pixie dust which may cause allergies in a lot of people (symptoms include frantically screaming at your screen about why this stuff doesn't work like you expect it to work). But they couldn't just change existing stuff or risk breaking the web. But they could avoid it for new stuff, and so they did that.
These APIs didn’t all come out the same time, so they weren’t designed to differ.
Instead they decided against doing live node lists in future but couldn’t change how the older methods work as that would break websites. In the world of DOM/JS you can’t really make breaking changes.
querySelector *does not* take 62ms to run. Both of them take 0.01ms at most, try it yourself. This is the sort of micro optimization you should not concern yourself with.
How often do you need to select unique elements by ID? Don't use IDs in the first place.
This is akin to using `i--` in loops to "speed up your code" — we're past that.
OP is explaining their methodology poorly. The 62ms number is the time it takes to run the call 100,000 times, in a loop, with a string interpolation. And measurement is done by taking the delta of two performance.now() calls, which are known to be precise to only about 1ms for spectre mitigation[0].
FWIW, JS old timers have known querySelector is slower than getElementById since querySelector became a thing.
I know, that's why it's misleading and it should not be considered. A quick reader will think that "using getElementById will save me 32ms", but it'd be off by several orders of magnitude.
That sounds like a pit of success, though. The gain just isn't that significant. IMHO, misleading would be if querySelector was faster under normal circumstances but shown to be slower through bad benchmarking
> How often do you need to select unique elements by ID? Don't use IDs in the first place.
wait a minute, does everyone NOT use IDs for unique elements on page (not each element)? I mean I am genuinely interested in knowing why not to use IDs. There are unique elements which need to be selected on a page like #logout or something.
It’s a common CSS wisdom to use ids very sparingly because there’s always a chance you may want to have the same element on the page multiple times.
There are some scenarios in which this is the case you may not expect right away, sometimes you need to duplicate elements for responsivity, for example.
I think for CSS the reason is mostly that IDs have very high specificity, so styling you apply there is very hard to override elsewhere in your stylesheet.
However, that just means that you will probably generally want to avoid using IDs in your selectors - not to avoid using them altogether, or in JavaScript.
They should have high specificity - and it should be hard to override them with other selectors - because they are specific. They can only select at most one element. Why would you want to override it? Then you'd have cruft (i.e. a bug in waiting) in the obvious place to look in the future.
Incorrect. An ID can be repeated on as many elements in a page as you like. It is only convention that makes an id “unique”. I am not suggesting you do it, since here be dragons e.g. I just got a stack trace in the console of jsbin.com on Mobile Safari when I was testing with two elements with the same id — it breaks developer’s assumptions.
Incorrect. getElementById returns a single element, regardless of how many elements you put with the same id; furthermore, having multiple elements with the same id is a violation of the relevant standards, so browsers may do whatever they want at that point (including ignore the repeated id on ALL elements, ignoring it on SOME elements, or honoring it on all elements). And in fact various browsers at various times have done all 3 of those things. Even today both Chrome and Firefox do two of those things depending on how you look at it (getElementById returns a single element, but CSS selectors apply to all elements with the ID).
It is only convention that gives anything meaning. HTML attributes are given meaning by convention. The words I'm writing right now are given meaning by convention. That implies abolutely nothing about the meaning or validity thereof.
> the id attribute value must be unique amongst all the IDs in the element's tree ... The id attribute specifies its element's unique identifier (ID).
It is uncommon knowledge that id’s do not have to be unique.
document.querySelectorAll('#test').length will return 2 if there are two elements on the page with the same id=test attribute. I personally avoid using that ‘feature’ because it would confuse many people, but it is important to know if you run into certain bugs in code.
When using a component framework, I consider components that can only be put on the page once a bad practice. Sure you probably only have the user menu on the page once, but it shouldn‘t rely on that. Not the case in a storybook for example. If I use IDs, to associate aria labels etc, they are mostly randomized.
Yeah but they’re not necessary for selection. Label elements actually need an input with ID to function. The other useful use for IDs is for deep-links in articles.
But then again that doesn’t mean the id should be used for selection (in JS nor CSS) as it’s probably easier to target many elements with a class, should they ever co-exist. This is basically what you end up with if you build components anyway (even without specific frameworks)
wouldn't it be easier to select an item with ID than select one with a class name and then iterate over to find which one you want. I am talking about items which exist only once. I am still not clear on why not to use IDs in a page in your opinion.
and get the same result, namely the Element object representing that div.
When there are multiple matching elements on the page, results differ. From document.querySelector() you get back "the first Element within the document that matches the specified selector, or group of selectors" [1], and the spec defines the search for "first" as depth-first. [2] [3] Meanwhile, the docs for document.getElementById() [4] mention that "element IDs are required to be unique if specified", and this too we find borne out in the relevant spec. [5]
If there is a specified way for DOM implementations to behave when element IDs aren't unique in the document, I haven't yet been able to find it. In any case, given the liberality with which the docs are peppered with warnings and reminders that IDs must be unique, violating that invariant takes us outside the expectation of implementation guarantees, so it's not all that easy to complain if it behaves differently across browsers or otherwise isn't predictable.
So, for elements that exist only once, it's as easy to select them by class as by ID, and using a class has the additional benefit that it's one fewer refactor if you do decide to use that element more than once - if you use ID to begin with, you have to change that when you reuse the element. This is also not a meaningful difference in convenience, I think.
That is exactly how use it too. For unique items which 100% will be once on a given page. CSS can be applied too however re-usability of css styles for a given component no more is possible but that is an agreed upon side effect.
A few minification transforms actually kind of speed code up. If Babel hadn’t turned into a hydra this kind of transforms would have been right up its alley.
> ignores the first 5 results (to avoid caching effects).
This would be the correct approach if you're interested in the "sterile laboratory" performance of these APIs. But the average webpage is going to not be doing a bunch of throwaway work before it starts selecting elements.
I think it would actually be much more interesting to see the cold start results to see if they're comparable to each other. Hypothetically if e.g. GetElementById is only faster after the result has been cached by this simulation, then I think any conclusions about real world impact here could be misleading.
Unless you are accessing elements by ID ridiculously often, the time taken will be utterly unnoticeable rounding error (far below one millisecond). But if the difference is enough to skew the benchmarks, then it makes perfect sense to remove them. (In practice, on removing the skipping, I see no significant evidence that it actually makes a difference.)
I'm a little surprised. Sure i'd expected getElementById to be faster but honestly I'd have expected browser implementation of querySelector to do a relatively trivial up front check, is the selector a simple id, if so, call getElementById. I suppose that adds overheads to all queries, but that's true of many types of "best case" optimizations. (in the best case it's faster but adds some overhead for any non-best case)
Still, I don't care about this level of optimization. I'll just contuinue to use querySelector everywhere because it's more flexable. No code I've ever written looks up so many elements in a single interaction that this micro optimization would ever matter to me.
Both getElementById and querySelector are quite fast, down to the level of measuring individual branches in a micro benchmark. querySelector does have to do a bit more work to lookup the cached query and a handful of extra branches over getElementById, it's not scanning the document for an ID query unless you're in quirks mode though.
Flexible is a double-edged sword. `querySelector` is bringing in the added complexity of selector syntax, which is more stuff to think about that isn't relevant to what you're trying to do.
For example now you have to worry about whether there are any characters in the ID that need escaping in a selector (eg `.`), something that may not be easy to verify when formatting an ID out of variables.
So I'd suggest preferring getElementById for its directness, rather than for micro-optimisation reasons.
(In principle the same should be true for getElementsByClassName, but the live NodeLists returned by that method are a trap for the unwary, so neither option is ideal.)
> No code I've ever written looks up so many elements in a single interaction that this micro optimization would ever matter to me.
Exactly. This is the type of nano-optimization that is pretty common to see in e.g. SO answers. Sure, selecting _a hundred thousand_ items in a loop might be some milliseconds faster, but so what? How often are you selecting more than a few at most?
I’m surprised by the apparent magnitude of the difference between Firefox and Chrome. On my laptop I’m getting results roughly twice as fast as reported in the article, but still fairly similar ratios all round:
I’m also a touch surprised by the difference between getElementById and querySelector, because I vaguely recall querySelector being optimised in browsers for the ID case some years back so that there was negligible difference.
(P.S. seeing Firefox’s version number continuing to creep up on Chromium’s, soon to overtake, I wish browsers would scrap their version numbering systems and switch to YYYY.MM instead, or even YYMM like Windows if they want just one number. Can’t even claim user-agent sniffing hazards any more since they’re slightly killing those off and reaching three digits is going to cause some trouble anyway.)
As mentioned elsewhere, this is probably not testing what it thinks it is. The benchmark is doing a bunch of weird things, using timers that are not very precise, throwing away the first results, using template literals, etc. The results should be taken with a grain of salt.
I would prefer getElementById regardless of its performance because you may need escaping for CSS selectors, for example `getElementById('comment:1234')` vs. `querySelector('#comment\\:1234')`.
You could avoid that quite easily by using IDs that don't need escaping though.
That said, the fact escaping is necessary could point to part of the reason why querySelector is slower. There's obviously some additional parsing necessary just to work out what the developer is requesting. If you don't need to spend that CPU time then it's certainly better not to.
This is my subjective opinion, but I think getElementById makes the code easier to understand when you scan the code. Even if the query selector is simple, it still requires you to read the query to understand it's just a simple lookup by id
This is not measuring what the author thinks it's measuring in Chrome. The benchmark iterates through 100,000 sequential IDs, and does so 105 times.
For getElementById:
This is a map lookup every time.
For querySelector:
Chrome caches the parsed selector, but the benchmark doesn't use the same ID twice in any run, so the cache is not effective within a given run. Chrome also has a 256 query limit (per document) on the cache [1] which means that even though the benchmark runs 105 times, each time the browser is parsing 100,000 selectors since the cache would have the last 256 but it always starts at 0. querySelector does have a fast path [2] that calls getElementById which the benchmark hits, but the parsing cost is dominating.
So the benchmark is really measuring selector parsing vs a map lookup. Firefox might have a separate fast path for ID looking selectors that skips the real css parser. It might also have a larger cache.
Chrome's cache should probably be bigger than 256 for modern web apps , but even so that wouldn't help a benchmark that's parsing 100k selectors repeatedly since it doesn't make sense to have a cache that size just for micro benchmarks and real apps don't use 100k unique queries.
It occurs to me that, during querySelector execution, components of a selector which match only by ID (or maybe by ID at all) could be maybe linearly or sublinearly resolved by calling the native-code implementation of getElementById. Per at least the MDN docs [1] [2], both return an Element, so nothing downstream will see any difference.
If the entire selector is a single ID matcher, execution time for querySelector probably would not be that much longer than for direct calls to getElementById; depending on implementation there might not even be any more stack frames. (Which would be a pain and might not matter, but there are a few ways you could do it if it did.)
In iOS 14.8 on this iPhone 12 mini with about half a battery, the getElementById test took 20ms, and the querySelector test 47. Of course I don't know what the implementation is actually doing, but those times seem awfully close together compared to those the author quotes.
Interesting, but, if you are handling anywhere near 100,000 elements you should probably be maintaining references rather than querying the DOM each time.
This is totally unscientific, but I like to write experimental apps that manipulate the DOM heavily and Firefox is very often the fastest between Chrome and Safari in repainting the DOM (this wasn’t the case ~3 years ago).
There is a lot of cruft in Chrome's engine that accumulated over the years. Meanwhile, Firefox had a lot of the engine logic rewritten from scratch, using Rust.
This is less of an issue of Chrome being slow and more about measuring different things across the two browsers because of how the micro benchmark is structured.
Interestingly, getElementById was 62% slower than querySelector on my computer with FF94. I reckon this is a moot issue as neither of these is likely to be a bottleneck for a web application.