const - JS | lectureAdvanced DOM and Events

Efficient Script Loading - Async & Defer

Advanced DOM and Events

To finish this section on Advanced DOM and Events, let's take a quick look at different ways of loading a JavaScript file in HTML.

Up to this point, we've always used the regular way of including Javascript files in our HTML documents.

<script src="script.js"></script>

However we can also add the async or defer attribute to the script tag.

<script src="script.js" async></script>
<script src="script.js" defer></script>

And these two attributes have a big impact on how the browser loads and executes the script. Now, in the HTML file, we can write the script tag in the document head or usually at the end of the body. And these are the two situations we will be comparing in this lecture.

When we include a script without any attribute in the head, what will the page-loading process look like over time? Well, when the user loads the page and receives the HTML, the HTML code will start to be parsed by the browser. And parsing the HTML is basically building the DOM tree from the HTML elements. Then at a certain point, it will find our script tag, start to load the script file, and then execute the script. And during this process, the HTML parsing will actually stop. So it will wait for the script to be fetched and executed. Only after that the rest of the HTML can be parsed. And after that parsing, the DOMContentLoaded event will be fired.

This is not ideal because we don't want the browser to be just sitting there and doing nothing. This can have a huge impact on the page's performance. Plus, in this case, the script is executed before the DOM is fully loaded or ready. And again, that's not ideal. So never include a script in the head like this. That's why we usually put it at the end of the body so that all the HTML is already parsed when it finally reaches the script tag.

So, with the script tag at the end of the body tag, the page loading process is as follows:

  1. The HTML is parsed
  2. Then the script tag is found at the end of the document.
  3. The script is fetched and executed.

This is much better. So if you didn't know why we always put the script tag at the end of the body, now you know 😉. However, this is still not perfect because the script could have been downloaded before, while the HTML was still being parsed. This brings us to the async attribute.

When we use the async attribute on the script tag in the document head, the script is fetched or loaded at the same time as the HTML is parsed. That is, in an asynchronous way. And that's already an advantage since the page load time is reduced. However, the HTML parsing still stops for the script execution.

With defer, the script is still fetched asynchronously, but the execution of the script is deferred until the end of the HTML parsing. So, in practice, loading time is similar to the async attribute, with the key difference that with defer, the HTML parsing is never interrupted since the script is executed after the HTML parsing is done. And most of the time, this is what we want.

You might wonder why I didn't talk about async and defer in the body section. Well, the reason for that is that they simply make no sense in the body. Because in the body, fetching and executing the script always happens after parsing the HTML. Hence async and defer have no practical effect there.

Let's now see and compare some of the use cases for async and defer. One important thing about loading an async script is that the DOMContentLoaded event will not wait for the script to be downloaded and executed. Usually, DOMContentLoaded waits for all scripts to execute, but scripts loaded with async are an exception. This might happen when a big script takes a long time to load.

On the other hand, using defer forces the DOMContentLoaded to only get fired after the whole script has been downloaded and executed. And so this is the more traditional way that this event works.

Another very important aspect is that async scripts are not guaranteed to be executed in the exact order that they are declared in the code. So the script that arrives first gets executed first. On the other hand, defer scripts are guaranteed to be executed in the order in which they are declared. And that is usually what we want.

In conclusion, using defer in the HTML head is the best solution. Use it for your scripts and for scripts where the execution order is important. For example, if your script relies on some third-party library that you need to include, you will include that library before your script to use the library's code. And in this case, you have to use defer and not async.

For third-party libraries where the order of execution is not important, for example, an analytics software like Google Analytics, then, in this case, you can totally use async.

What's important to note here is that only modern browsers support async and defer. Hence they will get ignored by older browsers. So if you need to support all browsers, you must put your script tag at the end of the body and not in the head. That's because this is not a JavaScript but an HTML5 feature. And so you can't really work around this limitation like you can with modern JavaScript features.

Great! With this, you should now have a pretty good idea about different ways of loading JavaScript scripts. And as alway if you want to learn mor you can check the MDN docs, or this nice article on GeeksforGeeks.