Anatomy of a JavaScript file
JavaScript is a high-level, general-purpose language used across browsers, servers, command-line tools, and embedded runtimes; modern engines commonly combine interpretation with just-in-time compilation.
File extensions: .js, .mjs
Every part of the example below is labelled and explained. This page is one of 55 annotated tours on AnatomyOf, a free, open-source project by LunarWerx Studios.
What is inside a JavaScript file
Comment
Single-line // or multi-line /* ... */, ignored by the engine.
// marks the rest of the current line as a comment; /* ... */ spans multiple lines and cannot be nested. Comments are stripped before execution and have no runtime cost or effect on behavior. JSDoc-style block comments (/** ... */) immediately above a function or class are picked up by editors and tools like TypeScript for inline type hints and autocomplete, even in plain JavaScript files.
Import statement
Brings in functionality from other modules.
import { calculateTax } from './utils.js' pulls a named export into the current module's scope. ES module imports are static and hoisted: they are resolved and linked before any code in the file runs, which is what lets bundlers perform tree-shaking. A default export is imported without braces (import utils from './utils.js'), and import * as ns from './utils.js' gathers every export onto a namespace object. Node and modern browsers require either a .mjs extension, "type": "module" in package.json, or a <script type="module"> tag to treat a file as an ES module rather than CommonJS.
Variable declaration
Declares variables using const (constant) or let (reassignable).
const and let are block-scoped: the variable only exists within the nearest enclosing {}, and both sit in a "temporal dead zone" from the top of that block until their declaration line, so referencing them earlier throws a ReferenceError instead of silently yielding undefined. const prevents reassignment of the binding itself, though objects and arrays it points to remain mutable. The older var keyword is function-scoped (or global) rather than block-scoped, and declarations are hoisted with an initial value of undefined, which historically caused bugs inside loops and conditionals. Modern Jav
Function definition
Reusable block of code, defined with the function keyword.
A function declaration is hoisted in full, so it can be called earlier in the file than where it is written. It gets its own this, determined by how it is called (as a method, standalone, or via call/apply/bind), and it can be used as a constructor with new. Inside a function, return immediately produces a value and exits; without one the function implicitly returns undefined. Parameters can have default values (function f(x = 1)) and rest syntax (function f(...args)) to collect any remaining arguments into an array.
Control flow (loop)
Executes code repeatedly, e.g. for...of, while.
for (const item of items) iterates the values of any iterable (arrays, strings, Maps, Sets) without exposing an index counter. for...in instead enumerates an object's enumerable property keys and is generally avoided for arrays. while and do...while repeat based on a condition checked before or after each pass, respectively. break exits the nearest loop immediately and continue skips to the next iteration. Because for...of and .forEach()/.map() create a fresh binding per iteration when the loop variable is declared with let or const, closures created inside the loop body correctly capture each
Control flow (conditional)
Executes code based on a condition, e.g. if, else.
if/else if/else branch on the truthiness of an expression. 0, "", null, undefined, NaN, and false are all falsy, everything else (including "0" and empty objects/arrays) is truthy. The ternary operator (cond ? a : b) offers a compact expression form for simple branches. switch compares a value against several cases using strict equality (===) and falls through to subsequent cases unless each ends in break. Optional chaining (?.) and nullish coalescing (??) let common conditional-access patterns be written without an explicit if.
Object literal
Key-value pairs enclosed in {}.
An object literal like { total: 100, items: [] } creates a plain object with the given keys and values in one expression. Shorthand syntax lets { name } stand in for { name: name }, and computed keys ({ [key]: value }) let a variable supply the property name. Spreading another object into a literal ({ ...defaults, override: true }) produces a shallow copy with selected properties overridden, a common pattern for immutable updates. Object literals are also the shape returned by functions that need to hand back multiple named values at once.
Arrow function
Concise syntax for writing functions using =>.
Arrow functions have no implicit parameters beyond what is listed and, crucially, no own this, arguments, or super: they capture this lexically from the enclosing scope, which avoids the classic var self = this workaround needed with regular functions inside callbacks. A single expression body ((x) => x * 2) implicitly returns that value; a block body ((x) => { return x * 2 }) requires an explicit return. Because they lack their own this, arrow functions cannot be used as constructors (new throws) and are a poor fit for object methods that rely on the calling object. They shine as short callba
Function call
Executing a function by using its name followed by ().
A function call (calculateTax(total, taxRate)) evaluates its arguments left to right, binds them to the callee's parameters, and executes the function body, producing its return value (or undefined if none). JavaScript is dynamically typed, so no argument count or type checking happens at the call site itself. Method calls (invoice.total.toFixed(2)) additionally set this to the object the method was accessed from. Function.prototype.call, .apply, and .bind let code invoke a function with an explicitly chosen this and argument list instead.
Export statement
Makes functionality available to other modules.
export { createInvoice } is a named export, re-imported elsewhere with the same name via import { createInvoice } from '...'. A module may also declare export default once to mark a single primary value imported without braces and under any local name. Named exports are preferred when a module exposes several related bindings, since they support better static analysis, auto-import tooling, and tree-shaking than default exports. ES module exports (export/import) are statically analyzed at link time, unlike CommonJS's module.exports, which is a plain runtime object assignment.
Call stack
The one place JavaScript actually runs code, one frame at a time.
JavaScript has a single call stack, and therefore does exactly one thing at a time. Calling a function pushes a frame; returning pops it. While any frame is on the stack, nothing else in the page can run, which is why a long synchronous loop freezes the UI completely rather than merely slowing it down. This is the whole reason the rest of the machinery exists. Since the language cannot pause a function to go do something else, anything slow has to be handed off and picked up later, and "later" always means *after the stack is empty*.
Async APIs
Timers, network and I/O, which run outside the engine entirely.
setTimeout, fetch, and file or socket I/O are not JavaScript. They are functions the host (a browser or Node) provides, implemented in the host's own code and often on other threads. Calling one registers the work, returns immediately, and takes the callback for safekeeping. So setTimeout(fn, 0) does not run fn now and does not run it in zero milliseconds. It means "hand fn to the host, and let it queue it as soon as it can", which is a lower bound rather than a promise.
Task queue (macrotasks)
Finished host work waiting for its turn on the stack.
When a timer elapses or a response arrives, the host does not interrupt your code. It puts the callback in the task queue, where it waits. Each turn of the event loop takes **one** task from this queue, runs it to completion, and only then looks again. That "one per turn" rule is why a flood of events cannot starve rendering, and why two setTimeout callbacks never interleave: each runs start to finish before the next begins.
Microtask queue (promises)
Promise callbacks, which jump the queue.
.then() callbacks, await resumptions, and queueMicrotask go into a separate, higher-priority queue. After every task, and after the currently running script finishes, the engine drains the microtask queue **completely** before taking another task. This is the mechanism behind the classic ordering puzzle: a promise resolved immediately still runs after all synchronous code, but before a setTimeout(fn, 0) registered earlier. It also means a microtask that queues another microtask can loop forever and hang the page, because the drain never reaches the end.
The event loop
The rule that decides what runs next, and when.
The event loop is not a thread or a queue; it is a rule, applied forever: if the call stack is empty, drain every microtask, then take one task from the task queue and run it. Repeat. Everything people call "asynchronous JavaScript" falls out of that one sentence. Callbacks never interrupt running code, ordering between promises and timers is fixed rather than racy, and "non-blocking" means the stack empties quickly, not that anything ran in parallel.
Official JavaScript site · All languages on AnatomyOf
The interactive tour needs JavaScript. Enable it to hover a callout and trace it into the code.