Devs.site

Controlling the loading of your JavaScript code

Unlike most compiled languages that you might now, JavaScript doesn't have an entry point. The engine takes care of initialization and once the code is processed, it just starts from the beginning of it. But, of course, you need to have control over the order in which things happen. You need to make sure that your code runs only when other requirements are met (for example, HTML elements have been loaded and they can be accessed through the DOM interfaces). Because today JavaScript code tends to take its toll on performance, specially in web applications that load even megabytes of libraries and scripts, prioritizing and postponing the loading and execution of different components of your pages has become a required practice to improve user experience. You don't want blank pages, flickering elements or sluggish behavior.


As already mentioned, JavaScript doesn't have a main() function and you cannot specify where the code starts, but web browsers (which is the case that interests us) do have rules for loading content and they also have an event system.

How and when scripts are loaded and executed inside a browser

By default, JavaScript is loaded and executed as soon as it is found inside an HTML page

    <!doctype html>
    <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>Test</title>
            <script>
                var script1 = 'script1';
                var script2 = 'script2';
                console.log('Loaded ' + script1);
            </script>
        </head>
        <body>
            <div id="container">

            </div>
            <script>
                console.log('Loaded ' + script2);
            </script>
        </body>
    </html>

In this basic case, if you open the console, you will see how each portion of script is executed in the order it is found in the document. If you want to interact with the DOM, though, know that only the elements loaded before a script are available to said script. In the case above, only the script at the bottom of the body can access the container DIV.

    <!doctype html>
    <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>Test</title>
            <script>
                console.log(document.getElementById('container'));
            </script>
        </head>
        <body>
            <div id="container">

            </div>
            <script>
                console.log(document.getElementById('container'));
            </script>
        </body>
    </html>

Only the second script will find the element with the container ID because such element has been loaded before the script being executed. The script in the header will output null. Each script can also access the HTML element inside which it has been added.

    <!doctype html>
    <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>Test</title>
            <script>
                console.log(document.head);
            </script>
        </head>
        <body>
            <div id="container">

            </div>
            <script>
                console.log(document.body)
            </script>
        </body>
    </html>

This is why when using some external libraries in your code, you are advised to append the script at the end of the body, when all previous elements are already loaded. But for people that want to separate the HTML from the JavaScript and have all the scripts defined in the header, this is not an appealing solution.

If we want to load the script from an external file, we would do this in HTML5:

    <!doctype html>
    <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>Test</title>
            <script src="myscript.js"></script>
        </head>
        <body>
            <div id="container">

            </div>
            <script>
                console.log(document.getElementById('container'));
            </script>
        </body>
    </html>

    // myscript.js
    console.log(document.getElementById('container'));

Now, the script has to be loaded from myscript.js then executed as soon as it is ready. And the other elements of the page will only be loaded after this process is done. Just like in the first case, only the DOM preceding the script is available

How we can control when a script is loaded and executed

Once we have our flowchart in mind (or on paper) and we know what components should load first and what can be postponed, we have several ways of controlling the loading and execution of the JavaScript code. The first reason for doing this is to have the HTML document loaded as fast as possible. If you are interested in improving your page's relations with search engines, this is one of the first recommendations you will find. You don't want users or search engines to wait for the HTML content they are interested in just because some big JavaScript file setting up the behavior of some custom elements has to be loaded first. Mostly, that can be done after the page is loaded.

A simple boolean attribute called defer can enable this behavior in the browser. You have to add it to the script element like in the example:

    <!doctype html>
    <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>Test</title>
            <script src="myscript.js" defer></script>
        </head>
        <body>
            <div id="container">

            </div>
            <script>
                console.log('embedded script', document.getElementById('container'));
            </script>
        </body>
    </html>

    // myscript.js
    console.log('external script', document.getElementById('container'));

The browser won't wait for the script to be loaded then continue with the following elements. The script will be loaded asynchronously and when the entire document is ready, it will be executed. That is why in this case the embedded script will run first, as you can see in the console.

Another attribute is async, which works just like defer, but using it will make the browser run the script as soon as it is loaded, instead of waiting for the entire document to be ready. If you want both the document and the JavaScript code to be loaded and run as fast as possible, this is the recommended way. Unless you need the entire document to be ready.

    <!doctype html>
    <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>Test</title>
            <script src="myscript.js" async></script>
        </head>
        <body>
            <div id="container">

            </div>
            <script>
                console.log('embedded script', document.getElementById('container'));
            </script>
        </body>
    </html>

    // myscript.js
    console.log('external script', document.getElementById('container'));

If you refer to multiple scripts with async, they will execute in the order they are loaded. With defer they will execute in the order you have defined them in the code. In this case, the embedded script might run before the external one or vice versa, depending on how fast the external script is loaded. If cached, the external script might run before. Otherwise, the embedded script will run first. You can refresh your page multiple pages and see how the order in which these two run changes. Besides that, the container element is sometimes available to the external script and sometimes not. In order to make sure that the element has been loaded, before using it, we might do this in our external script.

    // myscript.js
    if (document.getElementById('container')) {
        console.log('external script', document.getElementById('container'));
    }

But it is unlikely that you only want some code to run only when an element happens to be loaded just by luck. We might want our async script to load some sections of code as soon as the script is loaded and other sections of code when the DOM is ready. It's the best trade-off we can afford. In such a case, we will use the events system.

Events and handlers

There are three browser events that can help us with this task.

When you need to make sure that everything is loaded before running your JavaScript code, window.onload is what you need. This is the most common case for websites that afford a bit of delay between the loading of the content and the execution of the client-side scripts.

    <!doctype html>
    <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>Test</title>
            <script src="myscript.js" async></script>
        </head>
        <body>
            <div id="container">

            </div>
            <script>
                var script3 = 'script3';
            </script>
        </body>
    </html>

    // myscript.js
    window.onload = function() {
        console.log('external script ' + script3);
    }

With this, you are sure every DOM element in the document is ready in the same status it has when being displayed to the user (with CSS applied to it, images, fonts and other resources loaded). Other scripts on which your code might depend are also loaded and are available globally.

The DOMContentLoaded event can be used when interacting with the DOM, ignoring if resources like CSS, fonts or images are loaded. It does wait for other scripts, though, if async is not used. However, if async is used, the DOMContentLoaded event might be fired before you set up the handler for the event and your code won't run.

    <!doctype html>
    <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>Test</title>
            <script src="myscript.js" async></script>
        </head>
        <body>
            <div id="container">

            </div>
            <script>
                document.addEventListener("DOMContentLoaded", function(event) {
                    console.log('embedded script', document.getElementById('container'));
                });
            </script>
        </body>
    </html>

    // myscript.js
    document.addEventListener("DOMContentLoaded", function(event) {
        console.log('external script', document.getElementById('container'));
    });

In this example, you will see that only the embedded script will run. The external script runs after the DOM is loaded so the event handler is not called. This is where readystatechange comes in handy. By using it, you make sure that you only fire the DOMContentLoaded event only when necessary. If the DOM has already been loaded by the time your async script starts, you just proceed, not needing to wait for anything.

    // myscript.js
    var fired = false;
    document.onreadystatechange = function () {
        if (document.readyState == "loading") {
            // add event only if DOM is still loading
            document.addEventListener("DOMContentLoaded", function(event) {
            fired = true;
                console.log('external script 1', document.getElementById('container'));
            });
        }
    }

    // if event not added because DOM was too quick to load
    if (!fired) {
        console.log('external script 2', document.getElementById('container'));
    }
Loading scripts dynamically

Sometimes, you have a bunch of scripts that you don't event need to be loaded after the page. They can be loaded on command later when you need them. This can be done with JavaScript at run-time, taking the burden from the browser. A piece of code like this can do it:

    // myscript.js
    window.onload = function() {
        var newscript = document.createElement('script');
        newscript.onload = function () {
            console.log('Script loaded');
        };
        newscript.src = "extrascript.js";

        document.head.appendChild(newscript)
    };

    // extrascript.js
    console.log('extra script', document.getElementById('container'));

You will see that extrascript.js is loaded and executed before the onload event is triggered. Also, because we already make sure in myscript.js that the DOM is available by using window.onload, the dynamically loaded script already has access to it, too.

Conclusions

This is all the information you need when facing the need to better organize how your code is loaded and executed in order to improve resources usage and pages loading times. The rule of thumb would be:

0 comments

Specify your e-mail if you want to receive notifications about new comments and replies