The problem: scripts block downloads
Let's first take a look at what the problem is with the script downloads. The thing is that before fully downloading and parsing a script, the browser can't tell what's in it. It may contain document.write() calls which modify the DOM tree or it may even contain location.href and send the user to a whole new page. If that happens, any components downloaded from the previous page may never be needed. In order to avoid potentially useless downloads, browsers first download, parse and execute each script before moving on with the queue of other components waiting to be downloaded. As a result, any script on your page blocks the download process and that has a negative impact on your page loading speed.
Here's how the timeline looks like when downloading a slow JavaScript file (exaggerated to take 1 second). The script download (the third row in the image) blocks the two-by-two parallel downloads of the images that follow after the script:
Here's the example to test yourself.
Problem 2: number of downloads per hostnameAnother thing to note in the timeline above is how the images following the script are downloaded two-by-two. This is because of the restriction of how many components can be downloaded in parallel. In IE <= 7 and Firefox 2, it's two components at a time (following the HTTP 1.1 specs), but both IE8 and FF3 increase the default to 6.
You can work around this limitation by using multiple domains to host your components, because the restriction is two components per hostname. For more information of this topic check the article “Maximizing Parallel Downloads in the Carpool Lane” by Tenni Theurer.
The important thing to note is that JavaScripts block downloads across all hostnames. In fact, in the example timeline above, the script is hosted on a different domain than the images, but it still blocks them.
Scripts at the bottom to improve user experienceAs Yahoo!'s Performance rules advise, you should put the scripts at the bottom of the page, towards the closing </body> tag. This doesn't really make the page load faster (the script still has to load), but helps with the progressive rendering of the page. The user perception is that the page is faster when they can see a visual feedback that there is progress.
Non-blocking scriptsIt turns out that there is an easy solution to the download blocking problem: include scripts dynamically via DOM methods. How do you do that? Simply create a new <script> element and append it to the <head>:
var js = document.createElement('script');js.src = 'myscript.js';
var head = document.getElementsByTagName('head')[0];
head.appendChild(js);
Here's the same test from above, modified to use the script node technique. Note that the third row in the image takes just as long to download, but the other resources on the page are loading simultaneously:
As you can see the script file no longer blocks the downloads and the browser starts fetching the other components in parallel. And the overall response time is cut in half.
DependenciesA problem with including scripts dynamically would be satisfying the dependencies. Imagine you're downloading 3 scripts and three.js requires a function from one.js. How do you make sure this works?
Well, the simplest thing is to have only one file, this way not only avoiding the problem, but also improving performance by minimizing the number of HTTP requests ().
If you do need several files though, you can attach a listener to the script's onload event (this will work in Firefox) and the onreadystatechange event (this will work in IE). Here's a that shows you how to do this. To be fully cross-browser compliant, you can do something else instead: just include a variable at the bottom of every script, as to signal “I'm ready”. This variable may very well be an array with elements for every script already included.
Using YUI Get utilityThe YUI Get Utility makes it easy for you to use script includes. For example if you want to load 3 files, one.js, two.js and three.js, you can simply do:
var urls = ['one.js', 'two.js', 'three.js'];YAHOO.util.Get.script(urls);
YUI Get also helps you with satisfying dependencies, by loading the scripts in order and also by letting you pass an onSuccess callback function which is executed when the last script is done loading. Similarly, you can pass an onFailure function to handle cases where scripts fail to load.
var myHandler = {onSuccess: function(){
alert(':))');
},
onFailure: function(){
alert(':((');
}
};
var urls = ['1.js', '2.js', '3.js'];
YAHOO.util.Get.script(urls, myHandler);