Are you always wondering how JS can understand our code, how scopes can be created?
You'll need to have a better mental model about how JavaScript engine handles our code in general. In this article, we are going through how JS scopes are created under the hood, step by step like:
- Compilation
- Parsing/Compilation phase
- Evidences of code compilation
- Briefly about lexical scope
What is Compiled and Interpreted?
Compilation is a process that turns your code into a list of instructions that your machine can understand
Interpretation is similar to compilation but instead of processing the whole source code, it will be processed line by line. Each line is executed before proceeding to process the next line.
Note: new JS engines actually use both compilation and interpretation to handle JS programs.
Step by step through compilation phase
JavaScript code is processed in 2 phases: Parsing/Compilation and Execution
Parsing/Compilation phase will be our main concern in this article.
Parsing/Compilation happens in 3 basic stages:
-
Tokenizing/Lexing: consider
var a = 7;
the program will likely to break this up into tokens:var
a
=
7
;
- Parsing: turn tokens into an Abstract Syntax Tree (AST)
<VariableDeclaration> var
<Identifier> a
<AssignmentExpression> =
<NumericLiteral> 7
- Code generation: take the AST and turn it into a set of instructions to actually create a variable called a and assign a value to it
Note: JS compilation only happens milliseconds right before the code is executed.
How do we know that compilation happens in 2 phases?
There are 3 cases that you can see that JS will handle your programs in at least 2 phases: parsing/compilation ⇒ execution
- Syntax errors
- Early errors
- Hoisting
Case 1:
Consider:
const foo = 'cat'
console.log(foo)
const error = #7'dog' // Throw a syntax error here
If you run this code, you can observe that the program will throw the error first instead of logging 'cat'
to the console.
This example shows that JS engine know about the syntax error on the third line before executing first and second line, by parsing the entire program before executing it.
Case 2:
Consider:
'use strict'
console.log('cat')
function saySomething(pet, pet) {
console.log(pet)
}
saySomething('dog', 'fish') // Uncaught Syntax error: Duplicate param name not allowed
Again here, how can JS engine throw the error without logging 'cat'
to the console first? The answer is the code must be fully parsed before any executions happen.
Note: duplicate param name is not allowed in strict mode, but it's allowed in non-strict mode.
Case 3:
Consider:
function myPet() {
var dogName = 'Doggy';
{
dogName = 'Bata'; // error
let dogName = 'Lucky';
console.log(dogName)
}
}
myPet()
// ReferenceError: Cannot access 'greeting' before
// initialization
Technically, the error is thrown because dogName = 'Bata'
is accessing variable dogName
before it's declared on the next line.
But why it does not access the variable var dogName
rather than accessing the let dogName
?
The only way JS engine would know is that:
- JS processes the code first ⇒ comes to the statement
let dogName = 'Lucky'
⇒ declare the a blocked-scope ⇒ set up all the scopes and their variable associations.
As you can see now:
- JS code is parsed before any execution
- Scope is determined as the program is compiled, and will not change during run-time.
Lexical scope
If you declare a variable with var
inside a function ⇒ the compiler handles this declaration as it's parsing the function ⇒ associates the variable with function's scope (the variable is accessible anywhere inside the function) .
If you declare a variable with let
or const
⇒ compiler handles this declaration ⇒ associates the variable with block's scope (variable is accessible inside the nearest {...} rather than its enclosing function).
While scopes are identified during compilation, they're not actually created until runtime.
The scope of one variable, function or block cannot be changed later.
Summary
Compilation is a set of steps that process the text of your code and turn it into a list of instructions that computer can understand.
JS code is processed in 2 phases: Parsing/Compilation and Execution
The Parsing/Compilation phase only happens in microseconds right before the code is executed
Scopes are identified during compilation
Lexical scope is controlled entirely by the place where functions, blocks, variables are declared
Thank you for your time.
Top comments (0)