The hard thing about JavaScript

cmgustin

Chris Gustin

Posted on April 4, 2023

The hard thing about JavaScript


Photo by Gabriel Heinzer on Unsplash

Among the many things that make JavaScript unique from other programming languages I’ve worked with, there’s one nuance that can really trip you up when you’re starting out. Because I’m self-taught, I was quite a ways into my efforts to learn the language before this nuance was explained to me clearly and once it was, JavaScript programming became a lot easier overall.

The unique thing is this : unlike other programming languages, with JavaScript it matters where you load your code in your HTML document. In PHP, you can weave your code in and out of the HTML (technically a PHP file that compiles to HTML), and with Python or C, you can use templating languages to inject conditionals or variables. The language itself handles generating the final HTML, so there’s no need to tell the document, “Load this script here.” But with JavaScript, the programmer is in charge of telling the document where to execute the script. And there’s a pitfall to this that may not be obvious.

The Pitfall

Let’s say you’ve learned some basic JavaScript, and you decide to test out your new skills by making a basic webpage. You add an h1 tag that says “Hello” with an id of hello and a JavaScript block that targets that element, and changes the text to read “Hello world”. Your code looks like this:

<html>
  <head>
  ...
  <script> 
    document.getElementById("hello").innerText = "Hello world";
  </script>
  </head>
  <body>
    <h1 id="hello">Hello</h1>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

You refresh the page and nothing happens. The h1 still says “Hello”. So you open the console and see an error that says Uncaught TypeError: Cannot set properties of null (setting ‘innerText’) which seems to indicate that document.getElementById("hello") is returning null , but you can see the h1 with an id of hello in the HTML document. Why isn’t JavaScript finding it?

Let’s say you rewrite your code like this:

<html>
  <head>
  ...
  </head>
  <body>
    <h1 id="hello">Hello</h1>
    <script> 
      document.getElementById("hello").innerText = "Hello world";
    </script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Now when you refresh the page, you’ll see the h1 text change to “Hello world”. So why didn’t it work when the script was in the head?

It may already be obvious, but the answer is that when an HTML document loads, it evaluates from top to bottom. During that loading process, when a JavaScript block is encountered, that script block is executed immediately, and while it executes, the document stops loading. So if a script block is inserted before the DOM elements it needs to interact with, the HTML document will fully execute that JavaScript block before continuing on to load the DOM elements that script block would have been looking for.

One fix then, as shown, is to simply insert your script after all the DOM elements have loaded, typically right before the closing body tag. This way, the DOM elements are loaded first, then the JavaScript block is executed.

The order that things are loaded matters. A lot.

But what if my script needs to load in the head?

There may be cases where your script absolutely needs to load in the head, but also needs to interact with DOM elements. Or maybe you’re writing a plugin or third-party code and won’t have control over where your script is added in someone’s project. Luckily, there’s a fix for this too.

The document emits an event that signals that all DOM elements have finished loading, called DOMContentLoaded . If we pair this with addEventListener , we can tell a JavaScript block to wait until that event fires before our code is executed:

document.addEventListener("DOMContentLoaded", function() {
  // All DOM elements have loaded, start executing code here
})
Enter fullscreen mode Exit fullscreen mode

So in the original example, instead of moving the code to the bottom of the page, we could rewrite the script like this:

<html>
  <head>
  ...
  <script>
    document.addEventListener("DOMContentLoaded", function() {
      document.getElementById("hello").innerText = "Hello world";
    });
  </script>
  </head>
  <body>
    <h1 id="hello">Hello</h1>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Now when the page reloads, the h1 text changes to “Hello world”. Because of the DOMContentLoaded event listener, the script block won’t execute until the document has finished loading and the DOMContentLoaded event is fired.

I think this is incredibly important to understand when starting out with JavaScript, and it’s a nuance that doesn’t come up in many other programming languages, so I think it’s easily overlooked. Especially since the tendency is to learn CSS first, and CSS does just fine with selecting and styling DOM elements, despite being loaded in the head. It’s not farfetched then to think JavaScript would behave the same way, although as demonstrated, it doesn’t.

Understanding this distinction can really help cut down on the overall JavaScript learning curve, and lots of wasted time troubleshooting bugs.

💖 💪 🙅 🚩
cmgustin
Chris Gustin

Posted on April 4, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related