The <script> tag is how HTML pages load or embed JavaScript. Scripts can live inline in the page, inside a <script> block, or in separate .js files loaded with the src attribute. Modern best practice is to keep JavaScript in external files and attach behavior using addEventListener instead of inline attributes like onclick.
Script loading strategy matters for performance and correctness. Attributes like defer, async, and type="module" control when and how your JavaScript runs relative to HTML parsing.
<button onclick="...">). Simple but not recommended.<script> block in the HTML page..js files loaded with src="/path/app.js".defer: Downloads script in parallel, executes it after HTML parsing, preserves order. Great default.async: Downloads and runs as soon as possible; execution order is not guaranteed. Best for independent scripts (analytics/ads).type="module" enables ES modules, scoped variables, import, and behaves like defer.nomodule lets you serve legacy bundles to older browsers that don’t support modules.Common patterns for including JavaScript on a page:
<!-- 1. External script with defer (preferred in <head>) -->
<head>
<script src="/assets/app.js" defer></script>
</head>
<!-- 2. External script with async (fire when loaded; order not guaranteed) -->
<script src="/ads.js" async></script>
<!-- 3. Internal script at end of body -->
<body>
...
<script>
document.addEventListener('DOMContentLoaded', () => {
const btn = document.getElementById('greet');
btn.addEventListener('click', () => alert('Hello!'));
});
</script>
</body>
<!-- 4. Module with legacy fallback -->
<script type="module">
// Module code is scoped (no globals). Imports allowed if you have separate files.
const sum = (a, b) => a + b;
window.__sum = sum; // expose only if you explicitly assign to window
</script>
<script nomodule src="/legacy-bundle.js"></script>
<!-- 5. CDN with integrity -->
<script src="https://cdn.example.com/lib.min.js"
integrity="sha384-..." crossorigin="anonymous" defer></script>
These buttons are wired up using addEventListener from regular scripts and modules. There are no inline onclick attributes in the HTML.
Ready.
defer in <head> for scripts that depend on the DOM. It preserves execution order and doesn’t block HTML parsing.async only for independent scripts that don’t rely on others (analytics, ads).type="module" uses strict mode, scopes variables, and supports import. Globals only appear if you attach them to window.onclick in HTML, attach listeners via JavaScript for cleaner markup and CSP compatibility.integrity + crossorigin for Subresource Integrity checks.defer can slow down first paint.async chains: don’t make async scripts depend on each other because execution order isn’t guaranteed.load/error events and try...catch around imports.<body> into <head> with defer and confirm the behavior is identical.onclick with a separate script that uses document.getElementById(...).addEventListener('click', ...).type="module" block that uses a dynamic import() when a button is clicked, and log something only after the module loads.integrity and crossorigin, and wire up script.addEventListener('load'/'error') to log success or failure.<script> tag loads or embeds JavaScript, either inline, internal, or external.defer is usually the best choice for page scripts that depend on the DOM and each other.async suits independent scripts where load order doesn’t matter.type="module" enables ES module syntax, scoped code, and modern loading behavior.addEventListener instead.