Initializing JavaScript - Old and Modern Methods

JavaScripts generally require an initialization routine to be run either onload or once the document has all the necessary components in place, in particular, DOM elements required for the code. Traditionally, the onload event has been used. But this presents some problems:

  1. If the old-school approach is used, such as assigning a function to window.onload, a second script can overwrite a previous script's onload handler, which prevents the script from working.
  2. Initializing onload causes unnecessary delay. Scripts only need DOM elements and perhaps style sheets to be available, but the load event isn't fired until all images and other resources have loaded.

This tutorial discusses both of these problems, but feel free to skip the old and jump down to information on the modern event model and on initializing when the DOM is ready

Onload the Old-Fashioned Way

Old-fashioned JavaScript might call a function onload either by including it in the body onload attribute or by assigning it to window onload.

<body onload="doSomething(arg)">

or:

window.onload = doSomething;

There can be problems combining code when using these approaches: competing onload handlers.

A novice might grab two scripts from an archive and place them in a page only to find that they do not work together. Perhaps one uses body onload and another window onload, or perhaps there are two separate assignments to window onload.

Sticking with old-fashioned solutions for the time being, you can include multiple function calls onload in the body tag by separating them with a semicolon:

<body onload="doSomething(arg); doMeToo()">

Or you can place multiple function calls inside an anonymous function assigned to window.onload as follows:

window.onload = function() { 
  fn1Name(args);
  fn2Name();
}

Although the above practices still work, they have fallen out of favor.

AddLoadEvent Functions

A better approach has been around and in broad use for quite some time: addLoadEvent functions, the original version most likely by Simon Willison.

function addLoadEvent(func) {
  var oldonload = window.onload;
  if (typeof window.onload != 'function') {
    window.onload = func;
  } else {
    window.onload = function() {
      if (oldonload) {
        oldonload();
      }
      func();
    }
  }
}

You can call as many initialization routines as you like with addLoadEvent and they will all be successfully added to the stack. However, multiple window.onload function assignments placed ahead of the first call to addLoadEvent can't be collected, and window.onload function assignments placed after addLoadEvent function calls will overwrite its handling of window.onload up to that point.

On to more modern approaches to find a better solution.

addEventListener/attachEvent

The DOM Level 2 mechanism for attaching event handlers, addEventListener, (and the roughly equivalent attachEvent method used by Internet Explorer versions 5 through 8), allows multiple event handlers of the same type to be attached to the same object without interfering with each other. Scott Andrew LePera is credited for the original cross-browser addEvent function:

function addEvent(obj, evType, fn, useCapture){
  if (obj.addEventListener){
    obj.addEventListener(evType, fn, useCapture);
    return true;
  } else if (obj.attachEvent){
    var r = obj.attachEvent("on"+evType, fn);
    return r;
  }
}

An addEvent function can be used to call multiple functions onload in addition to being useful for other event types as well. This approach also appears to coexist amicably with window.onload and body onload.

DOM Ready

Now to address the second problem posed above, that is, the unnecessary wait for all the document's resources to load when the code just needs to know when the DOM is ready. The DOMContentLoaded event fires when the HTML has been parsed. It is standardized in HTML5 and is supported by all up-to-date browsers, including Internet Explorer as of version 9.

If you want to provide support for older browsers, you can use jQuery's ready method or a similar function that serves the same purpose. There are numerous versions, many of them going to great lengths to provide DOM ready equivalent support for older browsers. A simpler approach is provided by the whenReady function from JavaScript the Definitive Guide 6th edition by David Flanagan:

var whenReady = (function() { // This function returns the whenReady() function
    var funcs = [];    // The functions to run when we get an event
    var ready = false; // Switches to true when the handler is triggered

    // The event handler invoked when the document becomes ready
    function handler(e) {
        // If we've already run once, just return
        if (ready) return;

        // If this was a readystatechange event where the state changed to
        // something other than "complete", then we're not ready yet
        if (e.type === "readystatechange" && document.readyState !== "complete")
            return;
        
        // Run all registered functions.
        // Note that we look up funcs.length each time, in case calling
        // one of these functions causes more functions to be registered.
        for(var i = 0; i < funcs.length; i++) 
            funcs[i].call(document);

        // Now set the ready flag to true and forget the functions
        ready = true;
        funcs = null;
    }

    // Register the handler for any event we might receive
    if (document.addEventListener) {
        document.addEventListener("DOMContentLoaded", handler, false);
        document.addEventListener("readystatechange", handler, false);
        window.addEventListener("load", handler, false);
    }
    else if (document.attachEvent) {
        document.attachEvent("onreadystatechange", handler);
        window.attachEvent("onload", handler);
    }

    // Return the whenReady function
    return function whenReady(f) {
        if (ready) f.call(document); // If already ready, just run it
        else funcs.push(f);          // Otherwise, queue it for later.
    }
}());

This function just uses onload for browsers that don't support DOMContentLoaded or the readystatechange event (for Internet Explorer prior to version 9). This would provide DOM ready functionality for the vast majority of site visitors.