DEV Community

Cover image for Scope in JavaScript - Behind The Scenes
Mahesh Pratap
Mahesh Pratap

Posted on

Scope in JavaScript - Behind The Scenes

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:

  1. On encountering const foo compiler will ask Scope if a variable named foo 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 named foo for that scope collection.
  2. Compiler then produces code for the Engine to execute. To handle foo = 4 assignment, Engine asks scope if there is a variable called foo 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 variable foo 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();
Enter fullscreen mode Exit fullscreen mode

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:

Scope flow

Source
[1]: You Don't Know JS: Scope & Closures By Kyle Simpson

Oldest comments (0)