Does a browser truly read JavaScript line by line OR does it make multiple passes?

First, you make an incorrect assumption: modern JavaScript is compiled. Engines like V8, SpiderMonkey, and Nitro compile JS source into the native machine code of the host platform.

Even in older engines, JavaScript isn't interpreted. They transform source code into bytecode, which the engine's virtual machine executes.

This is actually how things in Java and .NET languages work: When you "compile" your application, you're actually transforming source code into the platform's bytecode, Java bytecode and CIL respectively. Then at runtime, a JIT compiler compiles the bytecode into machine code.

Only very old and simplistic JS engines actually interpret the JavaScript source code, because interpretation is very slow.

So how does JS compilation work? In the first phase, the source text is transformed into an abstract syntax tree (AST), a data structure that represents your code in a format that machines can deal with. Conceptually, this is much like how HTML text is transformed into its DOM representation, which is what your code actually works with.

In order to generate an AST, the engine must deal with an input of raw bytes. This is typically done by a lexical analyzer. The lexer does not really read the file "line-by-line"; rather it reads byte-by-byte, using the rules of the language's syntax to convert the source text into tokens. The lexer then passes the stream of tokens to a parser, which is what actually builds the AST. The parser verifies that the tokens form a valid sequence.

You should now be able to see plainly why a syntax error prevents your code from working at all. If unexpected characters appear in your source text, the engine cannot generate a complete AST, and it cannot move on to the next phase.

Once an engine has an AST:

  • An interpreter might simply begin executing the instructions directly from the AST. This is very slow.
  • A JS VM implementation uses the AST to generate bytecode, then begins executing the bytecode.
  • A compiler uses the AST to generate machine code, which the CPU executes.

So you should now be able to see that at minimum, JS execution happens in two phases.

However, the phases of execution really have no impact on why your example works. It works because of the rules that define how JavaScript programs are to be evaluated and executed. The rules could just as easily be written in a way such that your example would not work, with no impact on how the engine itself actually interprets/compiles source code.

Specifically, JavaScript has a feature commonly known as hoisting. In order to understand hoisting, you must understand the difference between a function declaration and a function expression.

Simply, a function declaration is when you declare a new function that will be called elsewhere:

function foo() {

}

A function expression is when you use the function keyword in any place that expects an expression, such as variable assignment or in an argument:

var foo = function() { };

$.get('/something', function() { /* callback */ });

JavaScript mandates that function declarations (the first type) be assigned to variable names at the beginning of an execution context, regardless of where the declaration appears in source text (of the context). An execution context is roughly equatable to scope – in plain terms, the code inside a function, or the very top of your script if not inside a function.

This can lead to very curious behavior:

var foo = function() { console.log('bar'); };

function foo() { console.log('baz'); }

foo();

What would you expect to be logged to the console? If you simply read the code linearly, you might think baz. However, it will actually log bar, because the declaration of foo is hoisted above the expression that assigns to foo.

So to conclude:

  • JS source code is never "read" line-by-line.
  • JS source code is actually compiled (in the true sense of the word) in modern browsers.
  • Engines compile code in multiple passes.
  • The behavior is your example is a byproduct of the rules of the JavaScript language, not how it is compiled or interpreted.

All functions will be examined first by the browser before any code is executed.

However,

var foo = function(){};

This will not be examined, and thus the following will throw a TypeError: undefined is not a function

foo();
var foo = function(){};

It does take 2 passes. The first pass parses the syntax tree, a part of which is performing hoisting. That hoisting is what makes your posted code work. Hoisting moves any var or named function declarations function fn(){} (but not function expressions fn = function(){}) to the top of the function they appear in.

The second pass executes the parsed, hoisted, and in some engines compiled, source code tree.

Check out this example. It shows how a syntax error can prevent all execution of your script by throwing a wrench in the first pass, which prevents the second pass (actual code execution) from ever happening.

var validCode = function() {
  alert('valid code ran!');
};
validCode();

// on purpose syntax error after valid code that could run
syntax(Error(

http://jsfiddle.net/Z86rj/

No alert() occurs here. The first pass parsing fails, and no code executes.