Skip to content

Instantly share code, notes, and snippets.

@MostlyFocusedMike
Last active March 24, 2021 03:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MostlyFocusedMike/bb8ca53c1b90b683caaad3b0c9659384 to your computer and use it in GitHub Desktop.
Save MostlyFocusedMike/bb8ca53c1b90b683caaad3b0c9659384 to your computer and use it in GitHub Desktop.

The Main Pattern for Vanilla JS

How to deal with DOM elements

If you want to put your script tag in the <head> of a document, you're probably familiar with putting things into the DOMContentLoaded event:

document.addEventListener('DOMContentLoaded', () => {
    doThings();
    doOtherThings();
});

function doThings() { ... }

function doOtherThings() { ... }

However, this can lead to some issues when trying to read HTML elements in multiple functions. You might instinctively move the elements up a scope, but that won't work:

document.addEventListener('DOMContentLoaded', () => { ... });

// nope
const thing = document.getElementById('thing');
function doThings() {
    thing.class = 'whatever';
}

function doThings() {
    thing.class = 'now-this';
}

thing won't be accessible at that point because it's outside DOMContentLoaded, which means the JS interpreter will try to resolve it before the DOM is loaded, and come up empty. Putting elements in the loaded event won't work either:

document.addEventListener('DOMContentLoaded', () => {
    const thing = document.getElementById('thing');

    doThings();
    doOtherThings();
});

Because as you know, even though thing WOULD be loaded now, it's no longer in a higher scope than where the functions are defined. Again, our functions won't have access. So, what are we going to do? We could redefine thing inside every function, but that's not exactly DRY.

A more elegant solution is to use a main function. The idea is to put everything, every single line, inside a function, and then call that function on DOMContentLoaded:

const main = () => {
    const thing = document.getElementById('thing');

    function doThings() {
        thing.class = 'whatever';
    }

    function doThings() {
        thing.class = 'now-this';
    }
}

document.addEventListener('DOMContentLoaded', main);

By the time main gets called, we have full access to our DOM, so we can reference it anywhere inside main without issues, including in our functions.

Why not just put your script in the body

You absolutely could. This setup is essentially mimicking what would happen if you put it just before the closing <body> tag. Or is it? Apparently, some browsers can now load pages asynchronously, so even if you put your script at the bottom of the page, the DOM may not yet be fully done. Leading to inconsistent behavior. In some browsers. Maybe.

So if you want to fully cover your bets, the safest thing to do is wrap all your content in a function and then call that only once the loaded event fires. Whether or not it's in the <head> or <body> tag is up to you to determine. I can't seem to find a solid answer as to which is truly "best," which means as long as you can explain why you chose what you did to an interviewer, you're fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment