Friday, December 7, 2012

HTML ids and javascript

I randomly stumbled upon an article online that did this:

function fnChangeText(){
   var oTextNode = document.createTextNode("New List Item 1");
   var oReplaceNode = oItem1.firstChild.replaceNode(oTextNode);

<ul onclick = "fnChangeText()">
<li id = "oItem1">List Item 1</li>

And I thought to myself, "Hey, they didn't access the LI element with document.getElementById!"

I fired up my javascript debugger on any random page and sure enough, if I saw html like this:
<div id="super_sweet_id"> ... </div>

And then in the javascript console did this:
> super_sweet_id
▶ <div id="super_sweet_id"> ... </div>
I thought to myself this couldn't be true. Maybe chrome was doing something fancy. But I was able to do the same thing in firefox and IE.

One caveat was that if the id had dashes, for example: my-id, the name wouldn't show up.  This is the same problem you see when you try to create an object:

// this isn't allowed in javascript
var obj = {my-id: 1};
▶ SyntaxError: Unexpected token -

// instead you have add keys with the associative array setter:
var obj = {};
obj['my-id'] = 1;

I figured this was the same problem with the variables I was seeing. The way to access ids with a dash is to use the window object because all the variables are really put on the window object.
> window['lame-dashed-id']
▶ <span id="lame-dashed-id"> ... </span>
// here is an example using the dot operator instead:
> window.awesome_id
▶ <div id="awesome_id"> ... </div>
Heck, you could even use this idea in anonymous functions, where the this variable is bound to the window object:
> (function() { return this.your_id; })();
▶ <body id="your_id"> ... </body>
Ok so, after figuring all that out, I wanted to find out if I could trick this. I went to some random page with jQuery and said, "What if I add two elements with the same id, heh heh heh (with a sneaky laugh)"
> $('body').append('<div id="a" />')
> $('body').append('<div id="a" />')
> a
 [ <div id="a"></div>, <div id="a"></div> ]

Wow, I felt like I found out some great secret of javascript. It even gave me multiple elements when there were more than one. But then I did these actions, and they made me lose all hope in this feature:
> a = 'what?'
> a
> $('body').append('<div id="a" />')
> a 
> (function() { return this.a; })();
// noooooooooo!!!!

So, in conclusion, you can clobber these variables pretty bad and not recover from the issue unless you do something like this:

> delete a
> a
 [ <div id="a"></div>, <div id="a"></div> ]

*whew* back to normal. Also this is a fun thing to try out:
> b
 <div id="b">Texty Text</div>
> = 'foo'; // muhuhahahahah!
> b
▶ ReferenceError: b is not defined
> foo
 <div id="foo">Texty Text</div>

This is a neat feature of javascript. However, I'm wondering if this could actually cause bugs in your code where you name a variable the same as the id of your element and then reference the variable outside of the scope of the function. There's kind of too much magic and gotchas you need to pay attention to.


  1. This comment has been removed by the author.

  2. relevant stackoverflow

    I think this would qualify as one of those "bad parts" of JavaScript :)

    Doesn't seem worth it when document.getElementById accomplishes the same thing without deadly magic.

  3. Good observation Joe.

    if you define a variable in a function, you should use 'var' to get a function local scope right. Then after that the variable will continue to be in a lexical scope due to closure. There should not be any problems.

    In your example, the reference to the global scope is not exactly clobbering global variable. Is it possible to change the attributes of the element with id 'a' by assigning values to a.course = 'cs'? I don't think so.

  4. @Srini, I think you would have to do a.setAttribute('course', 'cs');
    (or use jquery or whatever does the same thing)