My Javascript Execution Context Journey
Ibrahim Elhofy
Posted on May 31, 2021
Introduction
Execution Context is a concept if you understand it, will make it easier for you to understand more deeper concepts like :
- Scope
- Scope Chain
- Closure
- Event Loop
So in this article we will dive in this mysterious concept to understand what really happens under the hood in addition to digging a little bit deeper to know how Execution Context
works with Web APIs
, let's go.
Execution Context
When a fragment of JavaScript code runs, it runs inside an execution context which is an abstract concept of an environment where the Javascript code is evaluated and executed By environment, we mean the value of this, variables, objects, and functions JavaScript code has access to at a particular time.
When is an Execution Context
created ?
There are three types of code that create a new execution context:
- Global Execution Context (GEC) This is the default or base execution context When a script executes for the first time, the JavaScript engine creates it. All of the global code ( which is not inside any function or object is executed inside the global execution context ). GEC cannot be more than one because only one global environment is possible for JS code execution as the JS engine is single threaded.
Hint: In the Browser the Global Execution Context is created if there is no javascript script in the page or even when you do not have a single line of code in a .js file and load it, you will have the Global Execution Context created.
Function Execution Context It is an Execution Context like similar Global Execution Context gets created when function is invoked, but instead of creating the global object, it creates the arguments object that contains a reference to all the parameters passed into the function:
Eval Execution Context The underrated type in this Execution Context any code executed inside it also gets its own execution context, but because
eval
is underrated I will ignore it too and in the other hand it is also not recommended, so I will not discuss it here.
Execution Context Stack
When Execution Context is executing it is pushed into Execution Contexts Stack after it terminates, it popped off from the stack and this a whole process makes the javascript engine able to keep track off running function, or processes.
The way that Execution Context Stack works by can solve you the mysterious of how javascript is single threaded that is because you can't put multiple execution context in Execution Context Stack in the same time so you do one thing at run time
So the execution context stack can be conceptually represented as follows:
ExecutionContextStack [
ExecutionStack <global> { ... }
]
When you execute your script the javascript engine creates a new Execution Context called Global Execution Stack we will examine it deeper later in this article, then it pushes it in our Execution Context Stack
Execution Context Phases
Our Execution Context his life cycle walks through two phases:
The Creation Phase
In this phase The execution context is created. which the JS engine is in the compilation phase and it just scans over the code to compile the code, it doesn’t execute any code and in our Creation Phase there is a lot of things happen.
- LexicalEnvironment component is created.
- VariableEnvironment component is created.
Lexical Environment
According to ECMAScript 6 specification:
A Lexical Environment is a specification type used to define the association of Identifiers to specific variables and functions based upon the lexical nesting structure of ECMAScript code
Let’s try to simplify a few things here. A lexical environment consists of two main components: the environment record and a reference to the outer (parent) lexical environment:
Simply put, A Lexical Environment it's the internal Javascript Engine construct for let-defined variables. If you define a variable with let in a function , it is only visible within the function.
To keep all things simple let's put what we have just learned into code.
let globalLet = 1;
const globalConst = 2;
function globalFunction () { ... }
ExecutionContextStack [
ExecutionContext <global> {
lexicalEnvironment {
globalLet : <uninitialized>,
globalConst : <uninitialized>,
globalFunction : <ref to "globalFunction" function>
}
}
]
Each Lexical Environment has three components:
- Environment Record
- Reference to the outer environment,
- This binding.
Environment Record
You can think of it like a dictionary in which declarations of variables and functions are stored within the lexical environment.
let globalLet = 1;
const globalConst = 2;
function globalFunction () { ... }
ExecutionContextStack [
ExecutionContext <global> {
lexicalEnvironment {
EnviromentRecord {
globalLet : <uninitialized>,
globalConst : <uninitialized>,
globalFunction : <ref to "globalFunction" function>
}
}
}
]
There are three type subclasses inside of the Environment Record:
- Declarative environment record — As its name suggests, it stores variables, modules and classes, in addition to function declarations, you can think about this concept as instantiations of something you know, like maps or tables to store declared objects in it.
-
Object environment record — It is an Environment Record that associated with an object called
binding object
and for each of its properties a corresponding entry is created in the OER.
That is why you can access global variables such as window.document
that is because window
object is associated global execution context's object environment record any mutation on any on window
applies on the record
let globalLet = 1;
const globalConst = 2;
function globalFunction () { ... }
ExecutionContextStack [
ExecutionContext <global> {
lexicalEnvironment {
EnviromentRecord {
[[ type ]] : 'declarative'
globalLet : <uninitialized>
globalConst : <uninitialized>
globalFunction : <ref to "globalFunction" function>
}
// binding with `window` object
EnviromentRecord {
[[ type ]] : 'objective'
globalFunction : <ref to "globalFunction" function>
}
}
}
]
Reference to the Outer Environment
Every Environment Record has an [[ outerEnv ]]
a reference to the outer environment ( the environment from which it was called ) to make the javascript engine able to look for variables inside the outer environment if they are not found in the current lexical environment
let globalLet = 1;
const globalConst = 2;
function globalFunction (localArgument) {
let localLet = 3;
this.localProperty = 4;
}
new globalFunction(5);
ExecutionContextStack [
ExecutionContext <globalFunction> {
lexicalEnvironment {
EnviromentRecord {
[[ type ]] : 'declarative'
arguments : { 0 : <ref to "localArgument" argument>, length : 1 }
localLet : 3
}
// binding with `arguments` object
EnviromentRecord {
[[ type ]] : 'objective'
localArgument : undefined
}
// binding with `this` object
EnviromentRecord {
[[ type ]] : 'objective'
localProperty : undefined
}
this: <ref to "globalFunction" object>
}
}
ExecutionContext <global> {
lexicalEnvironment {
EnviromentRecord {
[[ type ]] : 'declarative'
window : <ref to "window" object>
globalLet : <unintialized>
globalFunction : <ref to "globalFunction" function>
}
// binding with `window` object
EnviromentRecord {
[[ type ]] : 'objective'
globalFunction : <ref to "globalFunction" function>
fetch : <built-in function>
setTimeout : <built-in function>
}
this: <ref to "window" object>
}
}
]
This Binding
- In the global execution context the value of
this
refers to the global object ( in browsers, this refers towindow
object ) - If it is called by an object reference, then the value of this is set to that object, otherwise, the value of this is set to the global object or undefined(in strict mode). For example:
this
refers to the reference object and it's value depending on variety variables, so :
- In Global Execution Context it refers to the global object (
window
in browser orglobal
in node js ) - In Functional Execution Context it's referencing depends on the way that function call it. if it called by an object reference, then the value of
this
is set to that object. Otherwise, the value ofthis
is set to thewindow
object or will be of valueundefined
( in strict mode )
let globalLet = 0;
function globalFunction (localArgument) {
let localLet = 1;
this.localProperty = 2
}
new globalFunction(3);
ExecutionContextStack [
ExecutionContext <globalFunction> {
lexicalEnvironment {
EnviromentRecord {
[[ type ]] : 'declarative'
arguments : { 0 : <ref to "localArgument" argument>, length : 1 }
localLet : <unintialized>
}
// binding with `arguments` object
EnviromentRecord {
[[ type ]] : 'objective'
localArgument : undefined
}
// binding with `this` object
EnviromentRecord {
[[ type ]] : 'objective'
localProperty : undefined
}
this: <ref to "globalFunction" object>
}
}
ExecutionContext <global> {
lexicalEnvironment {
EnviromentRecord {
[[ type ]] : 'declarative'
window : <ref to "window" object>
globalLet : <unintialized>
globalFunction : <ref to "globalFunction" function>
}
// binding with `window` object
EnviromentRecord {
[[ type ]] : 'objective'
globalFunction : <ref to "globalFunction" function>
fetch : <built-in function>
setTimeout : <built-in function>
}
this: <ref to "window" object>
}
}
]
Variable Environment:
It's also a Lexical Environment so it has all the properties and components that defined above, the purpose beyond that is storing the variable (var) bindings only.
var globalVariable = 0;
ExecutionContextStack [
ExecutionContext <global> {
lexicalEnvironment { . . . }
variableEnvironment {
// binding with `window` object
EnviromentRecord {
[[ type ]] : 'objective'
globalVariable : undefined
}
this: <ref to "window" object>
}
}
]
Execution Phase
After Creation Phase our Execution Context walks through the Execution Phase to bind variable initializations, variable assignments, mutability and immutability checking, variable binding deletions, function call execution, etc.
let globalLet = 0;
function globalFunction(localArgument) {
var localVariable = 2;
this.localProperty = 3
}
new globalFunction(4);
ExecutionContextStack [
ExecutionContext <globalFunction> {
lexicalEnvironment {
EnviromentRecord {
[[ type ]] : 'declarative'
arguments : { 0 : <ref to "localArgument" argument>, length : 1 }
}
// binding with `arguments` object
EnviromentRecord {
[[ type ]] : 'objective'
localArgument : 4
}
// binding with `this` object
EnviromentRecord {
[[ type ]] : 'objective'
localProperty : 3
}
this: <ref to "globalFunction" object>
}
}
ExecutionContext <global> {
lexicalEnvironment {
EnviromentRecord {
[[ type ]] : 'declarative'
window : <ref to "window" object>
globalLet : 0
globalFunction : <ref to "globalFunction" function>
}
// binding with `window` object
EnviromentRecord {
[[ type ]] : 'objective'
globalFunction : <ref to "globalFunction" function>
fetch : <built-in function>
setTimeout : <built-in function>
}
this: <ref to "window" object>
}
}
]
At the end I advice you to read this article several times to fully understanding these all concepts
Thanks
Sources
stackoverflow - What is the scope of variables in JavaScript?
stackoverflow - What's the difference between "LexicalEnvironment" and "VariableEnvironment" in spec
medium - JavaScript/TypeScript: Execution vs. Lexical vs. Variable Environment
medium - Lexical Environment — The hidden part to understand Closures
youtube - The Scope Chain, 🔥Scope & Lexical Environment | Namaste JavaScript Ep. 7
youtube - How JavaScript Code is executed? ❤️& Call Stack | Namaste JavaScript Ep. 2
green roots - Understanding JavaScript Execution Context like never before
javascript tutorial - Understanding JavaScript Execution Context By Examples
medium - Understanding Execution Context and Execution Stack in Javascript
Posted on May 31, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.