The Parser: How JavaScript Actually Reads Your Code
You type some JavaScript. You hit run. And it works.
But between those two moments, something fascinating happens β your code gets read, understood, and transformed before a single instruction executes. That process is called parsing, and it's the very first thing the engine does.
Let's walk through it, step by step.
πΊοΈ The Big Picture: 3 Steps Before Execution
When the JavaScript engine receives your code, it does three things in order:
Your Code (text)
β
1. Tokenizing β breaks code into small labelled pieces
β
2. Parsing β builds a tree structure from those pieces
β
3. Compiling β turns the tree into something it can run
Today we're focusing on steps 1 and 2. Step 3 (compilation & JIT) is Post 3.
Step 1: Tokenizing β Breaking Code into Pieces
The very first thing the engine does is read your source code character by character and group them into meaningful chunks called tokens.
Here's a simple example:
const age = 25;
The engine sees this as:
| Token | Type |
|---|---|
const |
Keyword |
age |
Identifier |
= |
Operator |
25 |
Number literal |
; |
Punctuation |
That's it. The tokenizer doesn't care about meaning yet β it just labels every piece of your code.
π‘ Confused? Think of tokenizing like a spell-checker reading a sentence. It doesn't understand the sentence yet β it just identifies each word and punctuation mark one at a time. Only after that does it start to make sense of the grammar.
Step 2: Parsing β Building the Tree (AST)
Once the engine has a list of tokens, it needs to understand the structure of your code β how those tokens relate to each other.
It does this by building an Abstract Syntax Tree, or AST.
π‘ Confused by "Abstract Syntax Tree"? Don't let the name intimidate you. It's just a tree diagram that represents your code's structure. "Abstract" means it strips away things like whitespace and semicolons β only the meaningful structure remains. "Syntax" means it's based on the grammar rules of JavaScript. "Tree" because it branches out like a family tree.
Here's what the AST looks like for our example:
const age = 25;
VariableDeclaration (const)
βββ VariableDeclarator
βββ Identifier: age
βββ NumericLiteral: 25
Even for one line of code, there's a whole tree. Let's try something slightly more complex:
function greet(name) {
return "Hello, " + name;
}
FunctionDeclaration
βββ Identifier: greet
βββ Param: Identifier (name)
βββ BlockStatement
βββ ReturnStatement
βββ BinaryExpression (+)
βββ StringLiteral: "Hello, "
βββ Identifier: name
Notice how the tree captures the nesting of your code β the return is inside the function, the + operation is inside the return, and so on.
π‘ Why does this matter to me as a developer? Every modern tool you use β Babel, ESLint, Prettier, TypeScript β reads and manipulates this tree. When ESLint catches a bug or Prettier reformats your code, it's working with the AST. Understanding this helps you understand your tools.
π Lazy vs. Eager Parsing: A Smart Shortcut
Here's something interesting: the engine doesn't fully parse all your code upfront.
It uses two modes:
- Eager parsing β fully parse this right now, we'll need it immediately.
- Lazy parsing β just skim this for now, fully parse it later if it's actually called.
// This gets eagerly parsed β it runs immediately
console.log("app started");
// This gets lazily parsed β it's a function, only parsed fully when called
function heavySetup() {
// ... lots of code
}
π‘ Confused? Lazy parsing is like skimming a book's table of contents before reading each chapter in full. The engine skims functions it hasn't needed yet, saving time on startup. This is why large JavaScript apps can start up faster than you'd expect β the engine defers work it doesn't need right away.
This is also why wrapping everything in functions can sometimes hurt startup time β if the engine has to parse more eagerly than expected.
β When Parsing Goes Wrong: Syntax Errors
If your code breaks a grammar rule, the parser throws a SyntaxError before anything runs.
const x = ; // SyntaxError: Unexpected token ';'
The parser expected a value after =, but got ; instead. It can't build a valid tree from this, so it stops immediately.
π‘ Confused? This is why syntax errors feel different from runtime errors. A runtime error happens during execution (e.g.
TypeError: cannot read property of undefined). A syntax error happens before execution β the engine couldn't even read your code properly. Nothing runs until the syntax is valid.
π οΈ You Can See the AST Yourself
Want to explore this hands-on? Visit astexplorer.net and paste any JavaScript code. You'll see the full AST in real time β every node, every branch.
Try pasting:
const double = (n) => n * 2;
You'll immediately see how even a simple arrow function becomes a rich tree structure. It's the best way to make this click.
π Quick Recap
Here's what happens before your code runs:
- Tokenizing β the engine reads your source text and splits it into labelled tokens.
- Parsing β tokens are assembled into an AST that captures the structure of your code.
- The engine uses lazy parsing to skip functions it doesn't need yet β saving startup time.
- If your code has a grammar mistake, a SyntaxError is thrown and nothing runs.
β‘οΈ Up Next
Post 3 β JIT Compilation: How JavaScript Goes From Slow to Fast
Now that the engine has an AST, it needs to turn it into something the computer can actually execute. We'll cover bytecode, the Ignition interpreter, and how Turbofan turns your hot functions into blazing-fast machine code.
Tried astexplorer.net? Drop a comment with something surprising you found in your own code's AST!
π Connect with Me
If you found this post helpful, follow me on Dev.to for more insights on JavaScript internals, data structures, and algorithms!
Top comments (0)