Perfection Kills

by kangax

Exploring Javascript by example

onload=function(){} considered harmful

December 9th, 2009

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.

Categories: don'ts, ECMA-262, strict-mode

Comments (11)

  1. Gravatar

    Andrea Giammarchi said on Dec 10, 2009 @ 2:41

    just 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

  2. Gravatar

    Andrea Giammarchi said on Dec 10, 2009 @ 2:45

    P.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 = 123
    where 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.

  3. Gravatar

    kangax (article author) said on Dec 10, 2009 @ 6:06

    @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?

  4. Gravatar

    Andrea Giammarchi said on Dec 10, 2009 @ 7:14

    let 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 …

  5. Gravatar

    Jani Hartikainen said on Dec 20, 2009 @ 13:40

    What 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.

  6. Gravatar

    kangax (article author) said on Dec 20, 2009 @ 15:12

    @Jani Hartikainen

    Yes, only onload = ... is an undeclared assignment (and only in some browsers). window.onload = ... is assignment of “onload” property to window object. window is always a declared variable in browser environment (at least, I’m not aware of any browser that lacks window), 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 no addEventListener, nor attachEvent).

  7. Gravatar

    Justin Johnson said on Dec 28, 2009 @ 21:54

    I’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.

Trackbacks

  1. Perfection kills » Understanding delete said:

    [...] you might remember, undeclared assignment creates a property on a global object. That is unless that property is found [...]

  2. 深入理解JavaScript中的delete操作(翻译) · hello,JavaScript said:

    [...] you might remember, undeclared assignment creates a property on a global object. That is unless that property is found [...]

  3. 转载:理解delete | Piggy Snow's sweet home said:

    [...] 您可能还记得,未声明的赋值在一个全局对象上创建一个属性。除非它在全局对象之前的作用域中的某个地方可见。现在我们知道属性分配与变量声明之间的差异,后者设置了DontDelete 特性,而前者没有--应该很清楚未声明的赋值创建了一个可删除的属性。   01.var GLOBAL_OBJECT = this; 02.  03./* create global property via variable declaration; property has DontDelete*/ 04.var foo = 1; 05.  06./* create global property via undeclared assignment; property has no DontDelete */ 07.bar = 2; 08.  09.delete foo; // false 10.typeof foo; // "number" 11.  12.delete bar; // true 13.typeof bar; // "undefined" [...]

Leave a Comment

Please, don't forget to escape your input (<, > and &). Wrap code sections with <pre>

Allowed tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>