Decoding JS (Part - 1): Scope
Understanding the fundamentals - one console.log at a time ๐
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! :)