Closures & Scope in JavaScript

Ever wondered how functions remember their outer scope to execute? How scopes work in Javascript?
In this article, we will look at the core concepts like execution context, lexical environment, scope chaining and closures. These concepts define how Javascript execution works at its core.
Let’s look at them one by one:
Execution context(EC): Javascript runs on a call stack. Execution context can be seen as a stack frame created on the stack when a function starts its execution. It holds all the resources needed to execute that particular function. Few things to remember about EC:
- It holds reference to the lexical environment and the this keyword.
- There is a global execution context and a local execution context per function call.
Lexical environment(LE): It is an object in heap which contains the record of all the variables present in a function. It also holds the reference to its parent lexical environment.
- It contains variables and parent LE reference stored in [[OuterEnv]].
- Each block leads to creation of a new LE.
- LE actually makes scoping possible in Javascript.
Scope: Scopes define the visibility area of a variable.
- Each block has its own scope.
- Inner scope has access to its outer scope but vice versa is not true. So a block have access to variables present in that particular block and the parent block.
Scope chaining: Each scope has access to its parent and this goes on till the global scope which doesn’t have a parent. So, each lexical environment has a reference to its parent LE, this forms a chain which is referred to as a scope chain. Global LE has parent reference set to null.
Closure: Functions, unlike other blocks, can exist even when the execution context which defined that function is destroyed. Functions are actually objects and their reference can be passed across the execution contexts and they need the outer scope from where they were defined to execute. To resolve this issue, an internal property [[Environment]] is added to function when it is defined which holds the reference to the outer LE. So when that function executes, an EC is created along with LE. EC holds reference to LE and LE holds reference to the parent - [[OuterEnv]] which is taken from [[Environment]] property of function. So, the function wrapped with reference to its outer scope(LE) - [[OuterEnv]] is what we refer to as a closure.
Now, let’s look at the complete execution flow:
Javascript creates a global execution context to start with. In creation phase it reserves the spot for each variable, these variables are stored in heap inside LE. It creates a LE for each block and references to these LE are stored in the EC. Along with it EC also stores the this reference. It sets parent of each LE accordingly and null is set for the global.
When a function definition is encountered, it creates an object for the function and stores the parent LE reference inside it forming a closure.
It updates these LE on the heap during the execution phase.
When a function execution starts, a separate execution context and lexical environment is created. EC holds reference to the LE. LE gets the parent from closure.
If an inner function is defined inside, a closure is formed for it.
Function completes its execution and EC is destroyed but LE still remains as it is referenced by the inner function’s closure.
Now when inner function executes same steps are followed.
Lexical environments are cleaned by garbage collector when no reference points to them.
Subscribe to my newsletter
Read articles from Payal Rathee directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
