Scope in JavaScript - Behind The Scenes
Mahesh Pratap
Posted on September 11, 2021
Scope basically means " the chance or opportunity to do something". But what does it mean w.r.t JavaScript, and how do JavaScript engines interact with it to process any line of code. Let's find out-
What is Scope?
Scope collects and maintains a look-up list of all the declared identifiers (variables), and enforces a strict set of rules as to how these are accessible to currently executing code.
To understand this, let's briefly see how compilation works for a piece of code that the Engine encounters.
Steps involved in compilation
1. Tokenizing/Lexing
This involves breaking up a string of characters into small chunks, called tokens. For example, const foo = 4;
may be broken into const
, foo
, =
, 4
, and ;
.
A tokenizer breaks a stream of text into tokens, usually by looking for whitespace (tabs, spaces, newlines). A lexer is basically a tokenizer, but it usually attaches extra context to the tokens -- this token is a number, that token is a string literal, this other token is an equality operator.
2. Parsing
Turning a stream(array) of tokens and turning it into a tree of nested elements, which collectively represents the grammatical structure of the program. This tree is called Abstract syntax tree.
To see how an AST looks like follow this link.
3. Code generation
This involves taking an AST and turning them into executable code.
The JS engine is vastly more complex than just these three steps. For instance, there are steps to optimize the performance of the execution which we'll cover in another post. But when do scope comes to picture during these steps 🤔.
Here's when scope comes to the picture
Consider this expression const foo = 4;
. Once the compiler is done with tokenizing and parsing this expression, it'll go for code generation, and proceeds as follows:
- On encountering
const foo
compiler will ask Scope if a variable namedfoo
already exists for that particular Scope collection. If so, the Compiler ignores this declaration and moves on. Else, It asks Scope to declare a variable namedfoo
for that scope collection. - Compiler then produces code for the Engine to execute.
To handle
foo = 4
assignment, Engine asks scope if there is a variable calledfoo
accessible in the current scope collection. If so, the Engine uses that variable. Otherwise, it looks in the scope outside of the current scope until it finds variablefoo
or reaches global scope.
If the Engine eventually finds a variable named foo
, it assigns the value to it. Otherwise, it will raise a ReferenceError
.
Consider the following program:
const a = 4;
function baz() {
const b = 2;
function bar() {
console.log(b);
}
function fam() {
console.log(a);
console.log(c);
}
bar();
fam();
}
baz();
Consider the functions bar()
and fam()
in above program.
On encountering console.log(b);
in bar()
Engine will ask Scope if there is a variable named a
accessible in bar()
's scope collection. Scope will say "Nope, never heard of it. Go fish". Then the Engine will ask the same question to baz()
's Scope, and here is what it says "Yep, it's there. Here ya go".
The same steps are followed while running console.log(a);
in fam()
. The only difference is, Engine won't find the variable until it reaches the Global scope, where it'll find a
.
Next, I'll try and run console.log(c);
but won't be able to find it in the Global scope collection either. At last, the Engine will raise its hand and yell out a ReferenceError
Here is a representation of the flow:
Source
[1]: You Don't Know JS: Scope & Closures By Kyle Simpson
Posted on September 11, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.