Lexical Scope in JavaScript

Kishore Vadde
Kishore Vadde
March 04, 2019
#frontendengineering

Introduction

Despite the fact that JavaScript falls under the general category of "dynamic" or "interpreted" languages, it is, in fact, a compiled language. But still it is not as same as traditional compiled languages, which are compiled well in advance and their results are portable among various systems.

But, nevertheless, the JavaScript engine performs many of the same steps, though in more sophisticated ways than we may commonly be aware, of any traditional language-compiler.

There are three main units of JS Engine which involve in the process of JS code execution

  1. JS Engine execution thread
  2. Compiler
  3. Scope Manager

What is Lexical Scope?

The lexical scope can be defined as a set of rules and boundaries defined for variables in the program, while the program is compiled. These rules are static, which can only be referred to but not be changed while executing the program.

How JavaScript creates a scope?

Before executing any piece of code, JS engine gives it to the compiler, while compiler going through the code, as it encounters variables, it tells the scope manager to define the rules and take care of them. While executing the code, JS Engine will ask the scope manager for the information about the variables as it encounters.

Global scope vs functional Scope

JavaScript is commonly known as functional scoped language. Each function in the JS code gets its own scope. Along with functional scope, JS has global scope, which is defined at the top level and every function can have access to the global scope.

What is the need for scope?

Defining a scope for a variable inside program at compile time, helps the JS Engine to know where it has to look for that particular variable value. If we refer to a variable which is not in the currently accessible scope, JS Engine will throw a reference error.

Let’s get into an action

Let’s take this code snippet

function foo(a) {
   var b = a*2;
  
   function bar(c) {
       console.log(a, b, c);
   }
   bar(b*3);
}

foo(2);

Lexical scope in the above code snippet can be visualized as

Scope hierarchy of the code snippet

There are 3 scopes in the above code snippet.

Global Scope

 In the global scope we have only one function variable(function name) declared, that is foo.

Scope of foo

It has variables a, b  and bar declared inside the scope of foo. And also it has access to global scope

Scope of bar

It has variable c and also it has access to the scope of foo and global scope

Code execution process

The above code snippet execution process will be, it first goes through the JIT(Just In Time) compilation, while compilation, variables scopes will be sorted out(lexical scoping) by scope manager. And finally JS Engine will execute the code, while execution it looks for variables in different scopes based on current execution point.

Compiler

In compilation phase, there will not be any expression evaluation and function calling takes place, only variable and function declarations are handled.

Here is the overview of the steps compiler go through, while executing the above code

  • In global scope, finds function declaration with name foo informs scope manager about it
  • Jumps into function foo, In function foo scope, informs scope manager about the declaration of variables a, b and function declaration bar.
  • Jumps into function bar, in function bar scope, informs scope manager about the variable c declaration.

Scope Manager

While compilation, compiler informs the scope manager about the variables and function declarations in the current scope of its compilation. Scope manage puts variables and functions in different scope buckets(different memory locations). We can see the visualization of the above code snippet in the image of scope hierarchy.

JS Engine Execution Thread

Once the compilation is done, JS Engine execution thread will start the code execution. While executing the code, as it encounters variable, it looks for the value of a variable in the scope of current code execution.

Here is the overview of the steps, while executing the above code.

  • In the global scope, starts at foo(2), gets the function definition by referring to global scope. And calls it by passing an argument of value 2.
  • Jumps into foo, in scope of foo, evaluates a*2 and assigns it to the variable b(=4).
  • In scope of foo, at bar(b*3), fetches function definition for bar, by referring to scope of foo. And evaluates the expression b*3(=12) and calls the function bar. 
  • Jumps into bar, in scope of bar,  fetches definition for console.log from global scope. (first it looks in the current scope. I.e scope of bar, since it is not available, looks in the parent scope of bar, i.e foo, here also it will not be available, so goes one level up, that is global scope, which is the end. It finds there.)
  • In scope of bar, evaluates the arguments a, b and c. variable c is in the current scope, i.e scope of bar, but a and b are not available in the current scope.
  • Looks in the parent scope of bar, i.e scope of foo, finds the values for a and b.
  • Calls the console.log with values of a, b, c and executes it.   

References

https://github.com/getify/You-Dont-Know-JS

Front end master Deep JavaScript foundations by kyle simpson