onload=function(){} considered harmful
Harmful pattern
There seems to be a new pattern appearing on the web — attaching window load listener through undeclared assignment:
onload = function() {
/* ... */
};
I’d like to explain why it’s a good idea to avoid it.
A conventional approach to perform this task is to explicitly assign to window.onload property. That is, not counting other means like DOM L2 methods — addEventListener (as well as proprietary attachEvent), or intrinsic event attributes — <body onload="...">:
window.onload = function() {
/* ... */
};
How does it work?
A tempting “short” version takes advantage of Javascript loose nature with regards to variable declarations. In Javascript, assigning to undeclared variable actually creates a property on a Global Object — global property. Since Global Object in browsers is usually a window object (or at least it often behaves that way), undeclared assignment essentially results in creation of property on window. As long as Global Object and window are the same entity, window.onload = ... and onload = ... should have identical results. At least, that’s how it is in theory, and in practice there are more implications, as you will see later on.
So if two are identical, why would we ever prefer longer version?
Because shorter one relies on undeclared assignment.
Who cares?
Undeclared assignments have been frowned upon for a long time, and rightfully so. Global variables declared locally are hard to maintain and generally cause confusion. It’s not always clear whether such assignments are intentional or simply an oversight. It is why validators like JSLint have been emiting warnings when encountering them.
MSHTML peculiarities
Another reason to avoid undeclared assignments is due to rather destructive behavior of MSHTML DOM. When undeclared assignment happens in IE, an obscure error is thrown if identifier is named as id or name of one of the elements in a document:
<p id="foo"></p>
<form name="bar" action=""><p></p></form>
<script type="text/javascript">
try {
foo = 1;
}
catch(e) {
document.write(e); // TypeError: Object doesn't support this property or method
}
try {
bar = 1;
}
catch(e) {
document.write(e); // ReferenceError: Illegal assignment
}
</script>
Note that plain variable declarations in global scope, or explicit assignments have no such problems:
<p id="foo"></p>
<form name="bar" action=""><p></p></form>
<script type="text/javascript">
var foo = 1; // declares (and initializes) global `foo` variable
window.foo = 1; // assigns to a "foo" property of `window` object
this.foo = 1; // assigns to a "foo" property of Global Object
</script>
But “onload” is different!
Technically speaking, the case with onload = function(){ } can be considered an exception. After all, an intention to create global onload property is rather clear there. It’s also unlikely that there will be an element with such id/name (although, you never know!). There’s, however, another problem rising up, and that problem is strict mode of ECMA-262 5th edition — a standard for an upcoming version of ECMAScript language, approved officially only few days ago.
Strict what?
The premise of strict mode is to provide higher security level for an ECMAScript program (or part of it): avoid features that are considered error-prone or inefficient; employ stricter error checking; provide increased performance. One of such “stricter error checks” happens to be the one with undeclared assignments, which simply throw error:
"use strict";
onload = function() { // ReferenceError
/* ... */
};
window.onload = function() { // Works as expected
/* ... */
};
Now, it’s worth mentioning that not all browsers would throw error. Some of them (e.g. WebKit) actually have properties corresponding to event handlers — such as “onload” — declared implicitly, before script execution occurs. Those that don’t — such as Firefox or Opera — would miserably fail.
"onload" in window; // true in WebKit, but not Firefox or Opera
window.onload; // `null` in WebKit, `undefined` in Firefox or Opera
onload; // `null` in WebKit, ReferenceError in Firefox or Opera
Does it really matter?
It’s good to understand that strict mode is not a requirement, but is merely an option. It is there to provide stricter rules for those who need it, and are willing to cope with (and enjoy) consequences. So if you are planning to make your code “strict”, don’t forget to avoid undeclared assignments — even as innocent-looking as “onload” ones.
Andrea Giammarchi said on Dec 10, 2009 @ 2:41
#1just in case you are guessing, I have never used direct onload assignment in production since it is:
1 – obtrusive
2 – problematic
3 – ambiguous
4 – … etc etc …
When I write the onload = function(){} I am always implicitly referencing the event, not the technique to use to attach it.
You will never find in any of my official code such technique indeed and for twitter or posts purpose, I’ll probably always use the onload assignment.
Regards
Andrea Giammarchi said on Dec 10, 2009 @ 2:45
#2P.S. it’s not a global undeclared assignment, as I have said every execution is virtually:
with(window){ .... }so it works exactly as expected … undeclared assignment would be
whatever = 123where whatever is not a getter/setter of the global object.
Same demonstration is possible via
with(new XMLHttpRequest){
onreadystatechange = function(){}
}
I would not call that an undeclared assignment.
kangax (article author) said on Dec 10, 2009 @ 6:06
#3@Andrea Giammarchi
“Every execution” is not “virtually
with (window)“. There are 3 exact types of execution code, each with its own clearly defined semantics. Read the specs ;)And if `onload` is a getter/setter, as you say, why is a ReferenceError thrown?
Andrea Giammarchi said on Dec 10, 2009 @ 7:14
#4let me re-formulate … every browser JavaScript execution is, virtually or if you prefer metaphorically speaking, the equivalent of:
with (currentGlobalObject) { // our daily JavaScript code }where currentGlobalObject is the implicit outer scope of our code.
onload *is* a special setter since it is a valid event associated directly to the window object.
Try to write
onclick = function(){}without any declaration and that would be an undeclared assignment in the window object.Finally, I am talking about JavaScript as is right now, and not about ES5 conventions that could or could not be activated.
Nice you ignored my first reply though …
Jani Hartikainen said on Dec 20, 2009 @ 13:40
#5What do you think of using window.onload then, if just onload is harfmul?
While I agree that just onload is a bad idea, as you point out, I have used window.onload on several occasions. I think it’s a perfectly fine way to attach the main script entry point – simpler than other approaches and works across browsers.
kangax (article author) said on Dec 20, 2009 @ 15:12
#6@Jani Hartikainen
Yes, only
onload = ...is an undeclared assignment (and only in some browsers).window.onload = ...is assignment of “onload” property towindowobject.windowis always a declared variable in browser environment (at least, I’m not aware of any browser that lackswindow), so no harm there.Listening for window’s load event via
window.onload, while not standardized, is pretty reliable and is widely supported (e.g. it would be possible to use this property in older browser that have noaddEventListener, norattachEvent).Justin Johnson said on Dec 28, 2009 @ 21:54
#7I’ve seen this on SO and other places about the web and am worried about it as well. I agree that while it works in some/most cases, it works by relying on not one, but two gimmicks of the language (the first is that variables declared without var become part of the global scope, and the second being that the global scope is currently window).
While the global scope is unlikely to change from window any time soon, it is still initially unclear to the majority of seasoned JavaScript developers what exactly is going on with statements like onload = function() {}; (at least those that I have talked to about it). In fact, the first time I saw it, I had to run a few test cases to make sure it actually worked.
What’s worse is that this perpetuates both a lack of understanding, unsafe code, and bad practices. The notion that “explicit is better than implicit” is definitely applicable here, but even more so due to the multiple platforms and environments that JavaScript is expected to target. Well done.