JavaScript
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Types
-
Section 3: Strings and Numbers in Depth
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Objects and Arrays
-
Section 7: Maps, Sets, and Symbols
-
Section 8: Asynchronous JavaScript
-
Section 9: Object-Oriented and Prototypes
-
Section 10: The DOM
-
Section 11: Browser APIs
-
Section 12: Modern JavaScript (ES2015-ES2025)
-
Section 13: Functional Programming Patterns
-
Section 14: Error Handling and Debugging
-
Section 15: Testing
-
Section 16: Accessibility for JavaScript Developers
-
Section 17: Internationalization and Localization
-
Section 18: Performance
-
Section 19: Node.js Fundamentals
-
Section 20: Regular Expressions
-
Section 21: Design Patterns in JavaScript
-
Section 22: Security Basics
-
Section 23: Data Structures and Algorithms in JavaScript
-
Section 24: Practical Projects
-
Section 25: More Advanced Async Patterns
-
Section 26: More Object and Class Practice
-
Section 27: Working with Dates and Internationalization
-
Section 28: Web Components
-
Section 29: More DOM and Browser Practice
-
Section 30: Build Tooling for Vanilla JavaScript
-
Section 31: More Practice Projects
-
Section 32: Interview and Algorithm Practice
-
Section 33: Error Objects (MDN Reference)
-
Section 34: TypedArrays and Binary Data
-
Section 35: Reflection and Metaprogramming (MDN Reference)
-
Section 36: More Global Functions (MDN Reference)
4: Linking and Loading Scripts on a Page
Up until now, we've been playing around with JavaScript in isolated environments or small snippets. But when you actually start building a project, you have to figure out where that code lives in your HTML. It sounds trivial—just drop a script tag in, right? That's exactly where most people hit their first "why is this not working" wall.
The "Head" Trap and the Null Reference
The most intuitive place to put a script is in the <head>. It feels organized; you're declaring your dependencies up top before the content starts. But here is the problem: browsers parse HTML from top to bottom. When the browser hits a <script> tag, it stops everything. It pauses the construction of the page, fetches the script, and executes it immediately.
Imagine you're building a simple "Dark Mode" toggle. You have a button in your HTML with the class .theme-toggle, and your JavaScript looks like this:
const btn = document.querySelector('.theme-toggle');
btn.addEventListener('click', () => {
document.body.classList.toggle('dark-theme');
});
If this script is in the <head>, it runs before the browser has even reached the <body>. At the moment the code executes, .theme-toggle doesn't exist yet. The browser returns null, and your console screams with a TypeError: cannot read property 'addEventListener' of null. I've spent more hours than I'd like to admit debugging this exact issue early in my career because I assumed the browser "just knew" to wait.
Moving to the Bottom of the Body
The traditional "fix" for this was to move the script tag to the very end of the <body>, just before the closing </body> tag. By the time the browser reaches the script, it has already parsed the entire DOM. Your button is there, your images are loaded, and querySelector actually finds what it's looking for.
This works, and you'll see it in plenty of older tutorials. However, it's not ideal. If your JavaScript file is huge, the user is staring at a fully rendered page that looks finished but is completely non-interactive because the browser is still downloading that giant script at the bottom. It creates a weird "dead zone" where the UI is visible but nothing happens when you click.
Modern Loading with Defer and Async
Nowadays, we have better tools: the defer and async attributes. These allow us to put our scripts back in the <head> (where they are easy to find) without blocking the page from rendering.
- defer: This is usually what you want. It tells the browser: "Start downloading this script in the background, but don't execute it until the HTML is fully parsed." It's the gold standard for your main application logic because it ensures the DOM is ready and it maintains the order of your scripts.
- async: This is different. It downloads the script in the background, but the second it finishes downloading, it pauses the HTML parser to execute the script. I only use this for third-party scripts that don't care about your DOM or other scripts—think Google Analytics or an ad pixel.
If you're choosing between them, stick with defer. It gives you the organization of the head with the safety of the bottom-of-the-body approach, and it's much more performant for the end user.
📋 Practical Task
Fixing the Broken Theme Switcher
You've been handed a project where the "Dark Mode" toggle is completely broken. The JavaScript file theme-engine.js is correctly written to find a button with the ID #mode-switch and toggle a class on the body, but the console is throwing a TypeError because the script is running before the button exists.
Your Goal: Modify the HTML structure provided below. Move or modify the script loading mechanism so that the JavaScript executes only after the DOM has been fully parsed, ensuring the button is available when the script runs, while keeping the script reference in the <head> section for organization.
<!DOCTYPE html>
<html>
<head>
<title>Theme Page</title>
<script src="theme-engine.js"></script>
</head>
<body>
<button id="mode-switch">Switch Theme</button>
</body>
</html>There are no comments for now.