Just the other day on Stack Overflow, I saw a seemingly uninteresting question with the following code:
The user’s problem was that the call to click()
wasn’t executing correctly, but if he changed the name of the function it did. I’ll admit that I wasn’t really in the mood for this type of question, so I brushed it off and moved on. The subject arose again in the JavaScript chat room where Pointy, a frequent flyer in the JS tags, was attempting to get to the root of the problem. My initial thought was that the user had made a mistake, but at the same time I thought it couldn’t hurt to check it out. So I fired up jsFiddle and pasted in the code. It failed silently, so I checked a couple of other browsers with the same result.
It was obvious what was going on at this point – the <button>
element has a method on it named click()
and the browsers were forcing the lexical scope of the function to include properties of the current object. I made a quick and dirty change to verify this:
<button onclick="console.log(tagName)">try it</button><br />
Sure enough, the console output was BUTTON
. I’m sure many experienced developers will be unsurprised at this, knowing this is the expected behaviour all along but, honestly, I’ve never encountered this before (probably due to how I like to attach my events via JS in the first place) and I don’t remember reading it in any specification. Pointy was able to find this portion of the HTML5 spec, however:
Lexical Environment Scope
- Let Scope be the result of NewObjectEnvironment(the element’s
Document
, the global environment).- If the element has a form owner, let Scope be the result of NewObjectEnvironment(the element’s form owner, Scope).
- Let Scope be the result of NewObjectEnvironment(the element’s object, Scope).
This indicates that the JavaScript equivalent for an element contained within a form would be something like this:
element.onclick = new Function( "event", "with (document) { with (this.form) { with (this) { /* function code */ }}}" );
I verified this by quickly testing getElementById
without referencing the document
object first. It worked just fine.
Now, after seeing all this, I’m left with several questions in my head all revolving around one important question — why? — why does anyone feel like it is necessary to do all this? I can’t think of any positive reasons for it to be this way, only reasons for it not to be this way:
- The
document
object and the host element for the event are both very easily accessible from within the function’s scope. NewObjectEnvironment()
has taken a lot of flak over the years, the only thing that uses it is thewith
statement and Crockford likes to remind us thatwith
statements are harmful, like in this example, we might not know ifclick()
belongs to the element, the form containing it, the document or the window. It’s also very inefficient.
The only reason I can think of that this is in the HTML 5 specification is for backwards compatibility. This might be the way the coding dinosaurs wrote their HTML and JavaScript 15 years ago and this feature has survived since Netscape Navigator 2.0. In my opinion, this isn’t an excuse and it should have been deprecated and removed long ago. I don’t feel so badly about it for myself, though, I’ve got by for years without even encountering this behaviour and I’m sure it will never affect me in the future, unless I’m working with someone else’s code. Just one more reason to attach your event handlers using script, I suppose.