Probably the most fundamental, widely used, and most misunderstood method of the jQuery library is the ready() method (aka the “DOM Ready” method), commonly seen as $(document).ready() or simply $().ready() or even indirectly $(function(){...}). Why the third version is indirect? We’ll get to that later.
Many new web designers/developers that start to use jQuery usually write their first block of code starting with something like the following:
$(document).ready(function(){
alert("Hello Whirled");
});
However, if you ask most people what is happening here, most likely the response is, “When the DOM is ready, I show an alert popup with the text ‘Hello Whirled’”. However, there is no “DOM Ready” event in JavaScript; it is a rather ingenious method that will execute a block of JavaScript before the page has finished loading. The benefits to having some JavaScript executed before the page has finished loading are immense, for one may need to manipulate the DOM prior to the page fully rendering, possibly some user agent detection, or whatever. Waiting to execute that code using the traditional window.onload method doesn’t allow us to do these things. But what is really happening when you call the ready() method? I aim to sort that out from a technical perspective in this article.
Ready?
The ready() method does three things when it is called:
- Attaches listeners to the method by calling the
bindReady()function. - Checks to see if
jQuery.isReadyproperty is true and if it is, it executes the function immediately. - Otherwise, it adds it to the stack (array) of functions to be executed when
jQuery.isReadyis in fact true.
jQuery.fn.extend({
/* ... */
ready: function(fn){
// Attach the listeners
bindReady();
// If the DOM is already ready
if (jQuery.isReady)
// Execute the function immediately
fn.call(document, jQuery);
// Otherwise, remember the function for later
else
// Add the function to the wait list
jQuery.readyList.push(fn);
return this;
}
});
The majority of the magic happens inside the bindReady() function:
var readyBound = false;
function bindReady(){
if (readyBound)
return;
readyBound = true;
// Mozilla, Opera and webkit nightlies currently support this event
if (document.addEventListener) {
// Use the handy event callback
document.addEventListener("DOMContentLoaded", function(){
document.removeEventListener("DOMContentLoaded", arguments.callee, false);
jQuery.ready();
}, false);
// If IE event model is used
}
else
if (document.attachEvent) {
// ensure firing before onload,
// maybe late but safe also for iframes
document.attachEvent("onreadystatechange", function(){
if (document.readyState === "complete") {
document.detachEvent("onreadystatechange", arguments.callee);
jQuery.ready();
}
});
// If IE and not an iframe
// continually check to see if the document is ready
if (document.documentElement.doScroll && window == window.top)
(function(){
if (jQuery.isReady)
return;
try {
// If IE is used, use the trick by Diego Perini
// http://javascript.nwbox.com/IEContentLoaded/
document.documentElement.doScroll("left");
}
catch (error) {
setTimeout(arguments.callee, 0);
return;
}
// and execute any waiting functions
jQuery.ready();
})();
}
// A fallback to window.onload, that will always work
jQuery.event.add(window, "load", jQuery.ready);
}
That’s a good bit of code, so let’s break it down into digestible chunks.
Standards-Compliant Browsers
The first, and most trivial check, is to see whether or not the readyBound variable is true and if it is, then we’re done! Otherwise, let’s set it to true now because when we only want/need to bind the handlers once.
if (readyBound)
return;
readyBound = true;
Next, we see an example of browser feature detection being used for executing a particular block of code. Most standards-compliant browsers support the addEventListener() method. By passing the DOMContentLoaded event to the addEventListener() function we are telling the browser to “listen” for the event that is fired when a document’s DOM content has finished loading. The anonymous callback function that is passed as the second parameter to addEventListenter() is then executed when that event has fired. This anonymous event handler function removes itself (referenced via arguments.callee) from being bound to the document once it has been called. Finally, we call the jQuery utility method jQuery.ready(). We will discuss this method later on.
// Mozilla, Opera and webkit nightlies currently support this event
if (document.addEventListener) {
// Use the handy event callback
document.addEventListener("DOMContentLoaded", function(){
document.removeEventListener("DOMContentLoaded", arguments.callee, false);
jQuery.ready();
}, false);
// If IE event model is used
}
Internet Explorer is Special
In the next block of code, this bit of feature detection checks to see if Internet Explorer’s event model is used. To handle iframes, we need to “listen” for a “complete” response from the request sent by that iframe. document.attachEvent("onreadystatechange", function(){...}) handles this and fires the anonymous function when that response arrives. If it is in fact a “complete” response, then, similar to how we did before, the event is detached (no longer listening), document.detachEvent("onreadystatechange", arguments.callee) and the jQuery.ready() utility method is called.
// Mozilla, Opera and webkit nightlies currently support this event
if (document.addEventListener) {
// Use the handy event callback
document.addEventListener("DOMContentLoaded", function(){
document.removeEventListener("DOMContentLoaded", arguments.callee, false);
jQuery.ready();
}, false);
// If IE event model is used
}
else
if (document.attachEvent) {
// ensure firing before onload,
// maybe late but safe also for iframes
document.attachEvent("onreadystatechange", function(){
if (document.readyState === "complete") {
document.detachEvent("onreadystatechange", arguments.callee);
jQuery.ready();
}
});
// If IE and not an iframe
// continually check to see if the document is ready
if (document.documentElement.doScroll && window == window.top)
(function(){
if (jQuery.isReady)
return;
try {
// If IE is used, use the trick by Diego Perini
// http://javascript.nwbox.com/IEContentLoaded/
document.documentElement.doScroll("left");
}
catch (error) {
setTimeout(arguments.callee, 0);
return;
}
// and execute any waiting functions
jQuery.ready();
})();
}
If we are not using iframes, then some true JavaScript trickery is used, so naturally we first want to verify that we are in fact using Internet Explorer and that it is not an iframe:
if (document.documentElement.doScroll && window == window.top)
Now, the good part. We have a self-invoking function that first, checks to see if the jQuery.ready property is true, for if it is then we return. Next, we use a trick (hack) by Diego Perini to see if the DOM Content is loaded by using the doScroll() method in a try/catch polling loop. This triggers the jQuery.ready() utility method when no errors are returned by the doScroll(). Pretty slick and a nice find by Diego. The catch(error) statement here is worth noting as the line containing the code setTimeout(arguments.callee, 0); essentially keeps calling this anonymous function until there are in fact NO errors, which then calls the jQuery.ready() utility method.
(function(){
if (jQuery.isReady)
return;
try {
// If IE is used, use the trick by Diego Perini
// http://javascript.nwbox.com/IEContentLoaded/
document.documentElement.doScroll("left");
}
catch (error) {
setTimeout(arguments.callee, 0);
return;
}
// and execute any waiting functions
jQuery.ready();
})();
If all of this fails, then we simply attach a load event, to the window object, and THEN call the jQuery.ready utility method when this event has fired.
// A fallback to window.onload, that will always work jQuery.event.add(window, "load", jQuery.ready)
Where Am I?
Let’s recap where we’re at. By now, we have determined that either the DOM Content is loaded, regardless of the browser, OR we are falling back on the fail-safe window.onload() method. Now, we just need to execute the jQuery.ready() utility method.
jQuery.extend({
isReady: false,
readyList: [],
// Handle when the DOM is ready
ready: function(){
// Make sure that the DOM is not already loaded
if (!jQuery.isReady) {
// Remember that the DOM is ready
jQuery.isReady = true;
// If there are functions bound, to execute
if (jQuery.readyList) {
// Execute all of them
jQuery.each(jQuery.readyList, function(){
this.call(document, jQuery);
});
// Reset the list of functions
jQuery.readyList = null;
}
// Trigger any bound ready events
jQuery(document).triggerHandler("ready");
}
}
});
The jQuery.ready() utility method is concise and elegant in its design. It starts off by checking to see if the DOM is already loaded and if it is, then we skip to the last line of code in the ready method definition. If the DOM is not loaded, then we set it to be loaded if this method is called again some time in the future. Then we check to see if the readyList property of the jQuery object (an array of functions) has a “truthyness” value of true, as it is null by default. If it is true, then we pass this array of functions into the jQuery utility method, jQuery.each(), and iterate over each element of the array. The second parameter of the jQuery.each() method is an anonymous function that is executed over each iteration. In this case, we are calling each function in the readyList in the context of the document and passing the jQuery object parameter to each function so it may be used within that function call: this.call(document, jQuery);
After we have iterated over the array of functions we then set the jQuery.readyList value to null, since we just executed all of those functions there is no need to keep track of them for the next potential jQuery.ready() utility method call. Finally, we trigger any events that may have been bound to the ready event using the triggerHandler() method.
Indirect Call of Ready Method
Earlier I mentioned there was a third way of calling the jQuery.ready() method, namely $(function(){...}). By first glance, one may posit what exactly is going on here and how does it correlate to a ready() method call? First, we are creating a new jQuery object with the common $() notation. However, instead of passing in a selector such as $('#myDiv'), we are passing in an anonymous function. If you view the source code of jQuery 1.3.2, you will come across this block of code at line 80:
// HANDLE: $(function) // Shortcut for document ready } else if ( jQuery.isFunction( selector ) ) return jQuery( document ).ready( selector );
This code is found within the init() method of the jQuery object and is the final check in the init method to see if the value passed in, selector, is a function and if it is, then it returns a call to the ready() method passing in the anonymous function as the parameter. So the ready() method is still being called, just indirectly.
Thoughts?
Attempting to break the DOM Ready method down into bite-size pieces and still be comprehensible was no easy task. Please let me know if this article helps you out or if there is anything I can do to improve it. Thanks!
UPDATE: Hat tip to @getify for some quality feedback to improve this article.
Tags: JavaScript, jQuery
Browse Timeline
Comments ( 2 )
[...] http://www.subprint.com/blog/demystifying-the-dom-ready-event-method/ [...]
JQuery document ready | webdevils.com added these enlightening words on Apr 13 10 at 9:19 PMThis is an awesome article. You seem to really know your way around javascript.

