Chapter 2 of 28 · JavaScript for Beginners — Complete Free Tutorial
Adding JavaScript to HTML
Before JavaScript can do anything, the browser must load and run it. There are several ways to include scripts — and small details like defer decide whether your page feels fast or broken. This chapter covers every placement method and the production-ready setup.
Method 1: inline event attribute (avoid)
<button onclick="alert('Hi')">Click</button>
It works, but logic lives inside your markup, cannot reuse handlers, and is hard to test. Use it only for quick experiments.
Method 2: internal script
<head>
<script>
document.addEventListener('DOMContentLoaded', () => {
console.log('Page ready');
});
</script>
</head>
Fine for one-page demos, but every page that needs the logic must repeat the block, and the browser cannot cache it.
Method 3: external script (the winner)
<body>
<button id="btn">Click me</button>
<script src="/js/main.js"></script>
</body>
// /js/main.js
const btn = document.getElementById('btn');
btn.addEventListener('click', () => alert('It works!'));
Expected output: clicking the button shows an alert — logic in its own file, cached by the browser, reusable across pages.
Placement and loading: none, async, defer
A plain <script> in <head> is render-blocking: the browser stops parsing HTML to download and run the file. The two attributes fix that:
| Attribute | Download | Execute | Best for |
|---|---|---|---|
| (none) | Blocks parsing | Immediately, in order | Nothing modern — avoid in head |
async | Parallel | As soon as ready — order not guaranteed | Independent scripts (analytics) |
defer | Parallel | After HTML parsing, in document order | Your app scripts |
<head>
<link rel="stylesheet" href="/css/styles.css">
<script src="/js/main.js" defer></script> <!-- recommended -->
</head>
With defer: the page renders while the script downloads, the DOM is fully parsed before your code runs, and scripts execute in order — all three properties you want.
DOMContentLoaded: the safe starting gun
Scripts with defer (and scripts at the end of <body>) run after the HTML exists. If you must run earlier, wait for the event:
document.addEventListener('DOMContentLoaded', () => {
// Safe: every element below exists now
const title = document.querySelector('h1');
console.log(title.textContent);
});
Expected output: the heading's text — instead of the TypeError you get when querying elements before they are parsed.
Modules preview
Modern JavaScript splits code across files using ES modules — one script declaration powers both loading and importing:
<script type="module" src="/js/app.js"></script>
type="module" implies defer, enables import/export syntax, and runs in strict mode automatically. The full system is chapter 23.
Best-practice production setup
- One external entry file (or a few), linked with
defer(ortype="module"). - Root-absolute paths:
src="/js/main.js"— relative paths break on nested URLs (the same trap as CSS stylesheets). - Wrap DOM work in
DOMContentLoaded(or use defer, which guarantees it). - Minify and bundle before deploying; keep source files readable while developing.
Common mistakes
- Script in
<head>withoutdefer— white screen until the file downloads, and DOM queries fail because nothing is parsed yet. - Relying on
asyncfor dependent code — with order not guaranteed,app.jsmay run beforehelpers.js. - Querying elements that do not exist yet —
querySelectorreturnsnulland the next line throws. - Duplicate ids and double-binding — attaching the same handler twice doubles your logic; check for repeated
addEventListenercalls.
Practice checklist
- Move an inline
onclickinto an external file; confirm it still works. - Put a query at the top of a
headscript without defer, watch it fail — then fix it withdefer. - Compare Network-tab load order with and without
defer.
With the plumbing in place, it is time for real syntax — starting with Variables: let, const & var.
Frequently Asked Questions
JavaScript is the programming language of the web. It adds behaviour to pages: responding to clicks and input, validating forms, fetching data from APIs, updating the DOM without reloading, and much more. It also runs on servers (Node.js), in desktop and mobile apps, and in build tooling.
They are unrelated languages that share a similar name for historical marketing reasons. Java is a statically-typed compiled language; JavaScript is a dynamically-typed interpreted language designed for the browser. Knowing one does not imply knowledge of the other.
Helpful but not mandatory — HTML gives you the structure JavaScript manipulates and CSS the styling it toggles. If you know basic tags, classes and selectors, you have enough to start; the DOM chapters assume only that much.
Use const by default and let when the value must change. Avoid var entirely in modern code: it is function-scoped instead of block-scoped, hoists differently, and can create accidental globals. This course uses const and let throughout.
The Document Object Model is the browser’s in-memory tree of objects representing your HTML. JavaScript reads and modifies that tree — selecting elements, changing text, adding nodes, attaching event listeners — and the browser re-renders the page to match.
== compares after converting types (so 5 == "5" is true), while === requires the same value AND the same type (5 === "5" is false). Loose equality has famously confusing rules — use === and !== everywhere unless you have a specific reason not to.
JavaScript is single-threaded, so long-running work (timers, fetches, events) is handled by the browser and its callbacks are queued. The event loop moves queued callbacks onto the call stack only when it is empty, with promise callbacks (microtasks) taking priority over timers. It is why asynchronous code never freezes the page.
A Promise is an object representing a future value — pending, fulfilled or rejected — that can be chained and combined (Promise.all). async/await is syntax sugar over promises that lets asynchronous code read top-to-bottom with ordinary try/catch error handling.
Yes. Frameworks are built on exactly these fundamentals — components are functions, state is your variable discipline, effects are events, rendering is DOM work. Developers who learn vanilla first understand what the framework is doing and debug far faster.
Type out the examples instead of copying, build small end-to-end projects (expense tracker, weather app, quiz), handle every UI state (loading, empty, success, error), and revisit fundamentals while reading real code. Consistency beats volume — a little most days outpaces occasional marathons.