Named element IDs can be referenced as JavaScript globals(css-tricks.com) |
Named element IDs can be referenced as JavaScript globals(css-tricks.com) |
The whole thing is ripe for a redo. I know they get a lot of hate, but of all the big players in this space I think FB is the best equipped to do this in a way that doesn't ruin everything. I just wonder if they have an incentive (maybe trying to break the Google/MS hegemony on search?).
If FB could launch a browser on iOS that was in their walled garden, not only would it quickly receive wide adoption but it might become people's primary browser.
Not that I necessarily think that's a good thing, mind you.
Web developers have worked around quirks for as long as I can remember. The stack has many warts, but we learn to adapt to them. Like 90% of a web developer's job is working around gotchas, and will continue that way. A 'redo' might not be needed. Developers need something to moan about and need something to keep them employed :)
"If FB decided to try and break into search, then they might decide to attack the HTML/CSS/JS stack."
Not the other way around.
If FB says "hey install this app," they will install it.
It uses a property interceptor which is fairly slow in v8:
https://source.chromium.org/chromium/chromium/src/+/main:out...
to call this mess of security checks:
https://source.chromium.org/chromium/chromium/src/+/main:thi...
which has this interop surprise:
https://source.chromium.org/chromium/chromium/src/+/main:thi...
which in the end scans the document one element at a time looking for a match here:
https://source.chromium.org/chromium/chromium/src/+/main:thi...
In contrast getElementById is just a HashMap lookup, only does scanning if there's duplicates for that id, and never surprisingly returns a list!
getElement is slightly faster, but not by enough to care IIRC so I use querySelector for consistency and it's flexibility.
> One reason jQuery became so popular is because the DOM was painful
I would say that is the key reason, with everything else being collateral benefits. Assuming you combine element selection, dealing with legacy incompatibilities, and function chaining to reduce boilerplate code, under the same banner of "making the DOM less painful".
https://jsbench.github.io/#b39045cacae8d8c4a3ec044e538533dc
ProTip: Without numbers performance opinions are wrong by several orders of magnitude 80% of the time.
I believe it works because the global object ("globalThis") is the Window in either case; this is why JavaScript Modules can refer to "window" in the global scope without explicitly importing it.
<!DOCTYPE html><body>
<div id="cool">cool</div>
<script>
console.log(this); // Window
console.log(globalThis); // Window
console.log("script", cool.innerHTML); // script cool
</script>
<script type="module">
console.log(this); // undefined
console.log(globalThis); // Window
console.log("module", cool.innerHTML); // module cool
</script>
</body></html>
This seems like a missed opportunity. JavaScript Modules should have been required to "import {window} from 'dom'" or something, clearing out its global namespace.1: https://github.com/tc39/proposal-shadowrealm
Here's a minimal example (https://jsfiddle.net/wc5dn9x2/):
<img id="asdf" name="getElementById" />
<script>
// The img object
console.log(document.getElementById);
// TypeError: document.getElementById is not a function :D
console.log(document.getElementById('asdf'));
</script>
I tried poking around for security vulnerabilities with this but couldn't find any :(It seems that the names overwrite properties on document with themselves only for these elements: embed form iframe img object
Edit: Here's how I found this: https://jsfiddle.net/wc5dn9x2/1/
It's weirdly not that discussed on the web, most probably because it require a pretty specific situation.
var name = true;
typeof name; // "string", not "boolean"
Luckily, this is not true within ES modules which you probably use most of the time anymway. <script>
var _value = "test value";
Object.defineProperty(window, "testName", {
get: () => _value,
set: (value) => { _value = String(value) },
});
</script>
<script>
var testName = {};
// prints [object Object] string
console.log(testName, typeof testName);
var name = {};
// prints [object Object] string
console.log(name, typeof name);
</script>
the `var` doesn't create a new property since the getter and setter already exist.Other properties have the same behavior, for example `status`.
Note: there's also LegacyUnforgeable which has similar behavior: https://webidl.spec.whatwg.org/#LegacyUnforgeable
Even if you're not using modules, using an IIFE avoids all this by making your variables local instead of having them define/update properties on the global.
name = {first: "Jane", last: "Doe"}
isn't obviously unreasonable. Which actually sets name to the string "[object Object]".I must have never used "name" as a name for a global variable or just for ones that were strings.
It's great for hacking a tiny script together, however.
See for example this thread where Mozilla tried to not do this: https://bugzilla.mozilla.org/show_bug.cgi?id=622491
IDs were the only way to get a reference to an element early on if I'm remembering correctly. Or maybe the DOM API just wasn't well known. All the examples and docs just used IDs, that I can remember for sure.
document.getElementById
like I should have and everything worked fine. Like others in this thread, I recommend not relying on this behavior.In 2022, that alone is enough to wipe it from my toolbox as a web developer. Ain't nobody got time for that.
(... there are lots of other reasons it'd be bad practice to rely on this as well, although it's nice for debugging when available).
Something like “window.elements.myDiv”? I wonder why the decision to go straight to the root.
<div id="foo"></div>
<script>
const { foo } = document.all
// do something with foo
</script>
Don't use it though, it's deprecated as well[1].[1]: https://developer.mozilla.org/en-US/docs/Web/API/Document/al...
// proxy to simplify loading and caching of getElementById calls
const $id = new Proxy({}, {
// get element from cache, or from DOM
get: (tgt, k, r) => (tgt[k] || ((r = document.getElementById(k)) && (tgt[k] = r))),
// prevent overwriting
set: () => $throw(`Attempt to overwrite id cache key!`)
});
Now if you have <div id="something></div>
You can just do $id.something.innerHTML = 'inside!';The simplest possible syntax is to make named elements available globally, and if that clashes with future additions to the DOM API then well that's a problem for some future idiots to worry about.
as a strategy it worked pretty well, unfortunately
It has nothing to do with JS spec; it's part of the DOM as defined by the HTML spec.
{
let foo = 1
};
// foo is undefined here[1] https://css-tricks.com/named-element-ids-can-be-referenced-a...
This doesn't seem to be true as shown within this fiddle: https://jsfiddle.net/L785cpdo/1/
Bear in mind that only undefined elements will be declared this way
>So, if a DOM element has an id that is already defined as a global, it won’t override the existing one.
So, if a global has name of the id of a DOM element, it won’t override the existing one?
Wouldn't it be clearer to say globals always before DOM ids?
I wouldn't use it in production, but it's handy for banging together a proof-of-concept.
Should read “rigamarole”
Today I learned, it's both!
3: "abc”.replace("a", "b")
4: "1" * 2
Which result in 15mops and 74mops respectively. This test measures diameters of neutrinos so to say.Get my hopes up finding an old forum post asking my question, hoping to find answers. All the answers are "use Google/etc", which is how I got there.
This used to be done quite a lot in the early JS days when scope was kind of thrown out the window (no pun) and you just did whatever dirty thing you needed to in order to make a page work.
> It's has been explained enough times. It's just that looking things up for yourself seems to have gone out of fashion.
It appears you've countered your own complaint.
Also there's a performance cliff when you have a lot of unique ids (or selectors in use from JS).
When you hit the cache querySelector is primarily a getElementById call and then some overhead to match the selector a second time (which chrome should really optimize):
https://source.chromium.org/chromium/chromium/src/+/main:thi...
But if you have more than 256 selectors and ids in use:
https://source.chromium.org/chromium/chromium/src/+/main:thi...
You'll start to hit the selector parser a lot more and then querySelector will be a fair bit slower going through the CSS parser.
Many many years ago I recall querySelector starting out with a check for #someCSSIdentifier and shortcutting to the getElementById path, but maybe my memory is playing tricks on me.
var theFunction = condition ? "querySelector" : "getElementById";
...
document[theFunction](...)
it won't apply to if (condition)
document.querySelector(...)
else
document.getElementById(...)
As from the point of view of the runtime the latter has two call sites, and each one is monomorphic and will very quickly (first layer of the JIT tower generally) become a Structure/Shape/HiddenClass check on `document` followed by a direct call to the host environment's implementation function (or more likely the argument checking and marshaling function before the actual internal implementation).It is possible that the higher level JITs pay attention to the branch counts on conditions or use other side channels for the deopt, but for host functions it's generally not something that will happen as the JITs see natively implemented functions as largely opaque barriers - they only have a few internal (to the runtime itself) cases where they make any assumptions about the behaviour of host functions.
I expected that to be the case but I’ve actually measured it and it’s not always. It is, when the object being accessed has a consistent shape/hidden class, as you mention, but a lot of times they don’t. A weird case is native interfaces because while the host functions are opaque and you’d expect they have a stable shape the interfaces themselves are often mutable either for historical reasons or shortcuts taken in newer proposals/implementations. Accessing document.foo isn’t and can’t be monomorphic in many cases, even if it can be treated that way speculatively. But branchy code can throw out all sorts of speculation of that sort. I don’t know which level of the JIT this occurs at, I’m just speaking from having measured it as a user of the APIs.
This isn't me disagreeing, just me being surprised and trying to think of why the optimizer falls off.
JSC at least has flags on the structure that track which ones will bollocks up caching (e.g. the misery that is looking up things in the prototype chain if the object in question has magic properties that don't influence the structure).
One thought I have is if your test case was something like
if (a)
obj.doTheNativeThing()
else
obj.doTheOtherNativeThing()
(or whatever)and you primed the caches by having a being true/false be a 50/50 split, vs all one way. My thinking (I have not done any of the debugging or logging) is that the branch that isn't taken won't insert any information about the call target. I can see that resulting in the generated code in the optimizing layers of the JITs being something along the lines of
if (a)
call _actualNativeFunction
else
deopt
The deopt terminates the execution flow so then in principle the VM gets to make assumptions about the code state after the whole if/else block, but more importantly the actual size of the code for the function is smaller, and so if you were close to the inlining limit dropping the content of the else branch _could_ result in your test function getting inlined, and then follow on optimizations can happen in the context of the function that you use to run your test with. Even if there aren't magic follow on optimizations removing the intermediate call can itself be a significant perf win.Testing the performance of engines was super annoying back when I worked on JSC, as you have to try and construct real test cases, but that means competing with your test functions being inlined. JSC (and presumably other engines) have things you can do (outside of the browser context) to explicitly prevent inlining of a function, but then that is also not necessarily realistic. But it's super easy to accidentally make useless test cases, e.g.
function runTest(f) {
let start = new Date;
for (let j = 0; j < 10000; j++)
f()
let end = new Date;
console.log(end - start)
}
function test1() {
...
}
function test2() {
...
}
runTest(test1)
runTest(test2)
In the first run with test1, f (in runTest) is obviously monomorphic, so the JIT happily inlines it (for the sake of the example assume both functions are below the max inlining size). The next run with test2 makes f polymorphic so runTest gets recompiled and doesn't inline. Now if test1 and test2 are both small the overhead of the call can dominate the cpu time taken which means that if you simply force no inlining of the function you may no longer be getting any useful information, which is obviously annoying :D