Implicit getElementById's

October 2013, October 2016

What is the most useful JavaScript function, but also the most annoying to write? document.getElementById(), of course! It is so useful, and so long, that every decent JS library contains an alias to call it easily (generally $()).

But did you know that we often don't even need to call it? Indeed, all the modern browsers (including IE6+) allow to access any element by calling directly its id as a global var! Surprisingly, it's even in the specs!

Examples:

<div id="myId"></div>
<script>
  alert(myId);                            // --> [object HTMLDivElement]
  alert(myId.id);                         // --> "myId"
  alert(window.myId.id);                  // --> "myId"
  alert(this.myId.id);                    // --> "myId"
  alert(self.myId.id);                    // --> "myId"
  alert(top.myId.id);                     // --> "myId"
  alert('myId' in window);                // --> true
  alert(window.hasOwnProperty("myId"));   // --> true
</script>

So "myId" is clearly a property of window! However, it's not "enumerable", so we can't see it if we loop or make a console.dir() on the window object:


// Display the content of window:
console.dir(window);                      // myId is not present.

// Loop through the properties of window:
var present = false;
for(var i in window){
  if(i == 'myId'){
    present = true;
  }
}
alert(present);                           // --> false
    

It's also important to note that if an id is not a valid JS identifier (for example if it contains a "-" or starts with a number), you can't access it directly, but you can still access it with window["invalid-identifier"].

For the record, Mathias Bynens wrote a very interesting article about ids and Unicode. Crazy!

On my side, I experimented with this "feature" and found some funny things:

An implicit id can have the same name as a protected JS keyword, but needs a "window" prefix to be read:

<div id=function></div>
<script>
  alert(function) // SyntaxError: Unexpected token
  alert(window["function"]) // [object HTMLDivElement]
  alert(window.function) // [object HTMLDivElement]
</script>

An implicit getElementById can NOT overload a native global var, such as window.screen:

<div id=screen></div>

<script>
  screen.innerHTML = "screen";          // --> nothing happens
  alert(screen);                        // --> [object Screen]
</script>

Also, it can NOT overwrite a global JS var defined earlier:

<script>
  global0 = 1;
</script>

<div id=global0><</div>

<script>
  global0.innerHTML = "global0"; // --> nothing happens
  alert(global0);                // --> 1
</script>

A global var defined with the "window." prefix can overload an implicit getElementById:

<div id=global1></div>

<script>
  global1.innerHTML = "global1"; // --> the div contains the text "global1" 
  console.log(global1);          // --> [object HTMLDivElement]
  window.global1 = 1;            // overload global1
  console.log(global1);          // --> 1
</script>

If we try to overload an implicit getElementById with a global var defined without any prefix, this is what happens (the results differ according to the browser used):

<div id=global2></div>

<script>
  global2.innerHTML = "global2"; // --> the div contains the text "global2" 
  try{global2 = 1}               // Try to overload global2
  catch(e){alert(e)}             // --> TypeError on IE < 9 / no error on other browsers
  console.log(global2);          // --> [object HTMLDivElement] on IE < 9 / 1 on other browsers
</script>

Finally, if we try to overload an implicit getElementById with a global var defined with a "var", this is what happens:

<div id=global3></div>

<script>
  global3.innerHTML = "global3"; // --> TypeError: Can't set property 'innerHTML' of undefined
  console.log(global3);          // --> undefined
  var global3 = 1;               // --> Try to overload global3
  console.log(global3);          // --> 1
</script>

As you can see, even if the var global3 is defined later in the code, browsers hoist variable definitions at the beginning of their scopes, and that's why it still breaks the lines 1 and 2.


To conclude, this technique is useful in some particular cases where we don't want to use a heavy library nor call the long function document.getElementById(), especially in code golfing contests. But in real life, it's safer to use the good old native methods because the implicit document.getElementById comes with several traps, and can lead to hours of debugging...


Cheers!
@MaximeEuziere