Decoding JS (Part - 1): Scope

Decoding JS (Part - 1): Scope

Understanding the fundamentals - one console.log at a time ๐Ÿ˜

ยท

5 min read

Scope

The concept of scope specifies "where to look" for values of the identifiers in a program. JavaScript organizes scopes using functions and blocks.

Global Scope means global region, Local Scope refers to a local region or restricted region.

The scope chain in JavaScript refers to the set of places that would be looked at to get the value of an identifier.

Consider this example -

const firstValue = "I am here";
const secondValue = "here";

function searchForValue() {
  const firstValue = "Hey there!";
    function searchForNewValue() {
       const firstValue = "Hey there 1!";
       console.log(firstValue, secondValue)
    }
  return searchForNewValue();
}

searchForValue()

Which values would be logged on the console?

While searching for firstValue, first, we would look at the value of firstValue in the local scope of the function, if it is defined locally, that value is taken, else we look for its value in the immediate parent's scope.

In this case, we look for firstValue in searchForNewValue 's scope.

While searching for secondValue, we first look for it in searchForNewValue's scope, since there is no definition of secondValue in searchForNewValue's a local region, and we look for it in searchForValue's a local region, and finally, we look for it in the global space where we find the definition.

So the final values logged would be - Hey there 1! for firstValue and here for secondValue .

Lexical Scope

Lexical area refers to the definition area of an expression. The term Lexical means anything related to creating words, variables or expressions. Even a dictionary is referred to as a lexicon ๐Ÿ˜

Lexical scope is also referred to as static scope. The definition area of an identifier may not be the same as its invocation area.

Consider the below function -

function getMyName() {
    const myName = "Harshita";
    console.log(myName);
}

Here, the definition area of the identifier myName is the local scope inside the function getMyName.

When we execute our code, The browser's JavaScript engine creates a special environment to handle the transformation and execution of this JavaScript code. This environment is known as the Execution Context.

This execution context contains the code that is currently running and everything that aids in its execution.

There are two types of Execution Context in JavaScript -

  • Global Execution Context (GEC)

  • Function Execution Context (FEC)

The Global Execution Context is the base/default execution context where all the code that is not inside a function gets executed. Whenever the JavaScript engine receives a script file, it creates this global execution context.

The Function Execution Context is the execution context created by JavaScript to execute the code within a function whenever it is invoked.

The phases that an execution context goes through are -

  • The creation phase

  • The execution phase

In the creation phase, the properties of the execution context are defined and set. It involves - Creation of the variable object, Setting the scope chain and the value of this keyword.

For each var declaration encountered in the Global Execution Context, a property is added to the variable object that points to that variable and is set to undefined.

For function declaration, a property is added to the variable object pointing to it and stored in memory, hence functions are made accessible in the variable object before the code starts running.

Also in JavaScript, Blocks only scope let and const declarations, but not var declarations.

This process of storing variables and function declarations in memory, even before the code is executed is called Hoisting

Hoisting

Variable and function declarations are made accessible within the Execution Context even before the code starts running, they are stored in the memory of the current Execution Context's Variable Object.

Variable Hoisting

In variable hoisting, each variable declared with the var keyword is stored as a property in the current Execution context's variable object and its value is set to undefined.

Function Hoisting

Unlike other programming languages, in JavaScript, the opposite of calling functions first and defining them later works due to Hoisting.

Consider this example -

getMyName();

function getMyName() {
    const myName = "Harshita";
    console.log(myName);
}

Is a valid way of using functions in JavaScript. However, it must be noted that Hoisting works only for function declarations and not expressions.

So -

getMyName();

var getMyName = function() {
    const myName = "Harshita";
    console.log(myName);
}

This gives the below error -

That is because since getMyName is defined as a var, it gets hoisted as a variable and its value is set to undefined, hence the error.

Scope Chain

The scope chain is created after the creation of the variable object discussed above. In this phase, each Function Execution Context creates its scope - the space/environment where the variables and functions it defined can be accessed via Scoping.
The idea of the JavaScript engine traversing up the scopes of the execution contexts that a function is defined in, to resolve variables and functions invoked in them is called the scope chain.

this in the Global Execution context

In the Global Execution context, which is outside of any function, this refers to the global object - window object. So, functions and variables declared in the global scope with the var keyword, get created as properties of the window object.

This is the reason why the below expression results in true -

Also, Function Execution Context doesn't create its this object. Rather, it gets access to the one of the enviroment it is defined in.

For objects, this refers to the object itself.

The JavaScript Execution Stack

The Call Stack or Execution stack in JavaScript keeps track of all the Execution Contexts in the lifecycle of the JavaScript Program.

Whenever actions or functions occur, an execution context is created for them, and since, JavaScript is a single-threaded language, they are piled up into something called as a Call Stack

Thus, the Global Execution context is placed at the bottom of this stack, for each function declaration, an execution context is created for it and placed at the top of the Global Execution Context. It is then executed in a top-down manner.

Refer to the below diagram to see this in action -

Conclusion

These concepts form the fundamentals to understanding several common occurrences in Javascript, in the end helping you write better code and avoid bugs arising out of an incomplete understanding of concepts. So, keep decoding JS! :)

ย