Web Performance Calendar

The speed geek's favorite time of year
2010 Edition
Nicholas Zakas photo

Nicholas C. Zakas is a principal engineer at Yahoo!, where he is front-end tech lead for the Yahoo! homepage and a contributor to the YUI library. He is the author of Professional JavaScript for Web Developers (Wrox, 2009), Professional Ajax (Wrox, 2007), and High Performance JavaScript (O'Reilly, 2010). Nicholas is a strong advocate for development best practices including progressive enhancement, accessibility, performance, scalability, and maintainability. He blogs regularly at http://www.nczonline.net/ and can be found on Twitter via @slicknet.

Over the past couple of years, a lot of attention has been paid to blocking and non-blocking JavaScript in browsers, and with good reason. JavaScript can both block both the rendering of a web page as well as causing the page to become unresponsive. Thanks largely to Steve Souders and his research, engineers now know how important it is to use non-blocking JavaScript in pages. There are three ways to create non-blocking scripts in JavaScript.

The first is to use the defer attribute on <script> tags. By adding defer, the browser starts to download the JavaScript file immediately but does so in a way that doesn’t block rendering or downloading of other resources on the page. For example:

<script type="text/javascript" defer src="foo.js"></script>

When the defer attribute is present, the script(s) will not execute until after the entire page’s code has been loaded.

Deferred scripts execute before the DOMContentLoaded event and should execute in the order in which they appear in the document (this isn’t necessarily the case in Internet Explorer). The defer attribute is supported in Internet Explorer 4, Firefox 3.5, Safari 5, and Chrome 7.

The second is to use the HTML5 async attribute on <script> tags. Async scripts also begin to download immediately and in a non-blocking manner:

<script type="text/javascript" async src="foo.js"></script>

The difference between async and defer is that async scripts execute as soon as the script is downloaded, so the page may still be loading when the script actually executes. Another difference is that the order of execution for async scripts is explicitly not guaranteed, so an async script that appears later in the document might actually execute before one that appeared earlier in the page. The only guarantee is that async scripts will execute before the load event.

The third, and most popular, technique is to use dynamic script tags that are created via JavaScript. For example:

var script = document.createElement("script"); script.type = "text/javascript"; script.src = "foo.js"; document.getElementsByTagName("head")[0].appendChild(script);

When inserting a script dynamically, the non-blocking download begins immediately. The script executes as soon as it is downloaded completely. In most browsers, the order of execution is not guaranteed, though Firefox < 4 and Opera will execute the scripts in the order in which they were inserted. This general approach is supported in all major browsers.

These three techniques for non-blocking JavaScript ensure that the downloading of the resource doesn’t block either rendering or the download of other resources on the page during page load. The little-known or understood aspect of all three approaches is that they are all defined to block the load event. That means adding scripts using any of these techniques during page load will delay execution of the window.onload event handler until all scripts have executed. All browsers obey this behavior except for Internet Explorer (even through version 9).

Depending on your needs, delaying the load event may be okay, but generally you want this event to fire as quickly as possible across all browsers. If you’re using a progressively enhanced design where it’s okay for JavaScript to be loaded later, you may want to consider delaying the addition of script tags using a timer:

//doesn't block the load event setTimeout(function(){ var script = document.createElement("script"); script.type = "text/javascript"; script.src = "foo.js"; document.getElementsByTagName("head")[0].appendChild(script); }, 0);

By using a timer with a delay of 0, the code executes as soon as possible after the initial page load is complete. Keep in mind that this doesn’t guarantee the code will execute either before or after the window.onload event handler, it only guarantees that the script download does not block the load event. If you want to ensure that the JavaScript doesn’t start to download or execute until after the load event, you can insert it using the window.onload event handler:

//doesn't block the load event window.onload = function(){ var script = document.createElement("script"); script.type = "text/javascript"; script.src = "foo.js"; document.getElementsByTagName("head")[0].appendChild(script); };

This is the last piece of information you need to make the best decision as to when a page’s JavaScript should be loaded and with which technique. Deferred scripts, async scripts, and dynamically loaded scripts all will prevent the load event from firing until they’ve downloaded and executed; by adding a timer, you can ensure the script doesn’t block the load event and your window.onload event handler fires as quickly as possible.