JavaScript
Article
By Julian Motz

Quick Tip: Replace jQuery’s Ready() with Plain JavaScript

By Julian Motz

This article was peer reviewed by Mev-Rael and Tim Severien. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!

The ready method was implemented in jQuery to execute code when the DOM is fully loaded. Since it executes the given function when all DOM elements are available, you can be sure that trying to access or manipulate elements will work.

Before jQuery 3.0, the typical usage with a anonymous function looked like this:

$(document).ready(function() {
  // Handler for .ready() called.
});

jQuery 3.0 ready() Changes

Before the release of version 3, there were several ways you could call the ready method:

  • on the document element: $(document).ready(handler);
  • on an empty element: $().ready(handler);
  • or directly (i.e. not on a specific element): $(handler);

All above named variants are functionally equivalent. The specified handler will be called when the DOM is fully loaded, no matter on which element it was called. In other words, calling it on an image element $("img") versus the document element doesn’t indicate that the callback is fired when the specified element is loaded. Instead, it will be called when the entire DOM is fully loaded.

In jQuery 3.0, all other syntax methods except $(handler); are deprecated. The official justification is:

This is because the selection has no bearing on the behavior of the .ready() method, which is inefficient and can lead to incorrect assumptions about the method’s behavior.

Difference Between the Ready and Load Events

The ready event is fired when the DOM is fully loaded and accesses to elements are safe. The load event, on the other hand, is fired after the DOM and all assets have loaded.

The load event can be used as follows:

$(window).on("load", function(){
  // Handler when all assets (including images) are loaded
});

This waits not only for the DOM to be ready for interaction but also for images to be completely loaded (which can take time, depending on the image sizes).

For normal DOM manipulations you’ll probably not need the load event, But it might be the right choice if you’d like to show a loading spinner until all assets are loaded, for example, or if you’d like to do some calculations with image sizes.

You Probably Don’t Need jQuery.ready()

The ready method makes sure that code is only executed when all DOM elements are safe to be manipulated. But what does this mean? When you’re executing JavaScript code inside the <head> section of an HTML document then this would make sure that the code is executed when the browser has loaded all following elements (e.g. the <body> element) too:

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>.ready() tutorial</title>
    <script src="https://cdn.jsdelivr.net/jquery/latest/jquery.min.js"></script>
    <script>
      $(function(){ // .ready() callback, is only executed when the DOM is fully loaded
        var length = $("p").length;
        // The following will log 1 to the console, as the paragraph exists.
        // This is the evidence that this method is only called when the
        // DOM is fully loaded
        console.log(length);
      });
    </script>
  </head>
  <body>
    <p>I'm the content of this website</p>
  </body>
</html>

If you’re executing JavaScript as the last thing inside the <body>, you probably don’t need to wrap it inside ready(), as all elements you might try to manipulate or access are already loaded:

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>.ready() tutorial</title>
  </head>
  <body>
    <p>I'm the content of this website</p>
    <script src="https://cdn.jsdelivr.net/jquery/latest/jquery.min.js"></script>
    <script>
      var length = $("p").length;
      // The following will log 1 to the console, as the paragraph exists.
      console.log(length);
    </script>
  </body>
</html>

Plain JavaScript ready() Alternative

For modern browsers, and IE9+, you can listen for the DOMContentLoaded event:

document.addEventListener("DOMContentLoaded", function(){
  // Handler when the DOM is fully loaded
});

But, note that the callback will not be executed if the event has already fired. To make sure the callback is always run, jQuery checks the readyState of a document (reference) and, if it’s already complete, executes the callback immediately:

var callback = function(){
  // Handler when the DOM is fully loaded
};

if (
    document.readyState === "complete" ||
    (document.readyState !== "loading" && !document.documentElement.doScroll)
) {
  callback();
} else {
  document.addEventListener("DOMContentLoaded", callback);
}

You could also include the domReady library, which has already implemented this solution.

Older versions of Internet Explorer

For IE versions less than or equal 8, you could use the onreadystatechange event to detect the readyState of a document:

document.attachEvent("onreadystatechange", function(){
  // check if the DOM is fully loaded
  if(document.readyState === "complete"){
    // remove the listener, to make sure it isn't fired in future
    document.detachEvent("onreadystatechange", arguments.callee);
    // The actual handler...
  }
});

Alternatively you could use the load event, like jQuery does, as this will work in any browser. This also results in a time delay, as it’ll wait for all assets to be loaded. Note that you’ll also have to check the readyState in this solution, like explained above, to make sure the callback will still be executed even if the event has already fired.

Conclusion

If you’re searching for a plain JavaScript alternative for the ready method you can proceed with the DOMContentLoaded event. If your system requirements include IE < 9 you can use the onreadystatechange event.

If you’re using jQuery in your project you can safely proceed with using the built-in ready function, but remember to avoid using the (deprecated) ready() method on elements (e.g. $(document).ready()) as mentioned earlier.

And lastly, don’t forget that in many situations you may not need any of these solutions — just move your JavaScript before the closing </body> tag and you can be sure that the DOM has finished loading!

More:
  • I’ve been using DOMContentLoaded for years now. I’d rather not have to use a lib if I can avoid it. POJO sometimes gets the job done

  • There is one important misconception in your article which is fairly misleading. The equivalent of is the ‘interactive’ document.readyState. Not ‘DOMContentLoaded’.

    I haven’t said that. For me it’s even loading (tested with Firefox 51.0a2, Chrome 54, Opera 36, Internet Explorer 9). Source code:

    Test

    Test element access

    console.log(document.readyState);
    console.log(document.querySelector("#element").innerHTML);

    But that doesn’t matter. If the elements you’re accessing are located before the script, then everything will work. The above example not just logs loading but also the HTML content of #element.

    For example, your ‘callback’ snippet cannot check for document.readyState === “complete” and use a ‘DOMContentLoaded’ listener as fallback. The ‘complete’ state always occurs after ‘DOMContentLoaded’ (right before the ‘load’ event).

    It’s true that the complete state occurs after the DOMContentLoaded event. And that’s why it indeed can check for complete first, to make sure the callback is called even if the event was already fired. As mentioned this code is taken from the official jQuery repository on GitHub, therefore you can expect that it works with great cross-browser compatibility.

  • Instead of arguments.callee, name your function, then you can reference it by it’s name.


    document.attachEvent("onreadystatechange", function onReadyStateChange(){
    if(document.readyState === "complete") {
    document.detachEvent("onreadystatechange", onReadyStateChange)
    }
    })

    • That would be another possible way, yes :-)

  • Please describe what exact problem you have with this article.

  • Camilo Reyes

    Great read, I’ve been trying to convince teams to use this technique for years. For some strange reason, jQuery keeps folks in a perpetual state of confusion.

  • Jair Reina

    Thanks for the tip. The only thing to take into account is that as of ES5 the use of arguments.callee() is forbidden in strict mode.

    • Thanks for participating. Please see the comment from @Poetro.

  • Altiano Gerung

    defer attribut on script tag will solve it.

    • Good catch, but only for script tags with src attribute.

  • Great Info.

  • Nice an informative tips..

  • Odin Hassan

    Typo : “I you’re executing JavaScript as the last thing inside the”
    I -> If

    • Nilson Jacques

      Thanks for letting us know, Odin. I’ve corrected the article.

  • IvanC

    Hi Julian,

    Thank you for this. I’m using it and it looks to be workin good for me. Just one thing, could you please explain this part?

    (document.readyState !== “loading” && !document.documentElement.doScroll)

    I don’t get specifically this: !document.documentElement.doScroll

    Greetings.

Recommended
Sponsors
Get the latest in JavaScript, once a week, for free.